From ce4c302d7379260148ad23f8daeedb02fbe92161 Mon Sep 17 00:00:00 2001 From: veguAI <152010387+vegu-ai-tools@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:56:29 +0300 Subject: [PATCH] 0.32.0 (#208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * separate other tts apis and improve chunking * move old tts config to voice agent config and implement config widget ux elements for table editing * elevenlabs updated to use their client and expose model selection * linting * separate character class into character.pt and start on voice routing * linting * tts hot swapping and chunking improvements * linting * add support for piper-tts * update gitignore * linting * support google tts fix issue where quick_toggle agent config didnt work on standard config items * linting * only show agent quick toggles if the agent is enabled * change elevenlabs to use a locally maintained voice list * tts generate before / after events * voice library refactor * linting * update openai model and voices * tweak configs * voice library ux * linting * add support for kokoro tts * fix add / remove voice * voice library tags * linting * linting * tts api status * api infos and add more kokoro voices * allow voice testing before saving a new voice * tweaks to voice library ux and some api info text * linting * voice mixer * polish * voice files go into /tts instead of templates/voice * change default narrator voice * xtts confirmation note * character voice select * koboldai format template * polish * skip empty chunks * change default voice * replace em-dash with normal dash * adjust limit * replace libebreaks * chunk cleanup for whitespace * info updated * remove invalid endif tag * sort voices by ready api * Character hashable type * clarify set_simulated_environment use to avoid unwanted character deactivated * allow manual generation of tts and fix assorted issues with tts * tts websocket handler router renamed * voice mixer: when there are only 2 voices auto adjust the other weight as needed * separate persist character functions into own mixin * auto assign voices * fix chara load and auto assign voice during chara load * smart speaker separation * tts speaker separation config * generate tts for intro text * fix prompting issues with anthropic, google and openrouter clients * decensor flag off again * only to ai assisted voice markup on narrator messages * openrouter provider configuration * linting * improved sound controls * add support for chatterbox * fix info * chatterbox dependencies * remove piper and xtts2 * linting * voice params * linting * tts model overrides and move tts info to tab * reorg toolbar * allow overriding of test text * more tts fixes, apply intensity, chatterbox voices * confirm voice delete * lintinG * groq updates * reorg decorators * tts fixes * cancelable audio queue * voice library uploads * scene voice library * Config refactor (#13) * config refactor progres * config nuke continues * fix system prompts * linting * client fun * client config refactor * fix kcpp auto embedding selection * linting * fix proxy config * remove cruft * fix remaining client bugs from config refactor always use get_config(), dont keep an instance reference * support for reasoning models * more reasoning tweaks * only allow one frontend to connect at a time * fix tests * relock * relock * more client adjustments * pattern prefill * some tts agent fixes * fix ai assist cond * tts nodes * fix config retrieval * assign voice node and fixes * sim suite char gen assign voice * fix voice assign template to consider used voices * get rid of auto break repetition which wasn't working right for a while anyhow * linting * generate tts node as string node * linting * voice change on character event * tweak chatterbox max length * koboldai default template * linting * fix saving of existing voice * relock * adjust params of eva default voice * f5tts support * f5tts samples * f5tts support * f5tts tweaks * chunk size per tts api and reorg defaul f5tts voices * chatterbox default voice reog to match f5-tts default voices * voice library ux polish pass * cleanup * f5-tts tweaks * missing samples * get rid of old save cmd * add chatterbox and f5tts * housekeeping * fix some issues with world entry editing * remove cruft * replace exclamation marks * fix save immutable check * fix replace_exclamation_marks * better error handling in websocket plugins and fix issue with saves * agent config save on dialog close * ctrl click to disable / enable agents * fix quick config * allow modifying response size of focal requests * sim suite set goal always sets story intent, encourage calling of set goal during simulation start * allow setting of model * voice param tweaks * tts tweaks * fix character card load * fix note_on_value * add mixed speaker_separation mode * indicate which message the audio is for and provide way to stop audio from the message * fix issue with some tts generation failing * linting * fix speaker separate modes * bad idea * linting * refactor speaker separation prompt * add kimi think pattern * fix issue with unwanted cover image replacemenT * no scene analysis for visual promp generation (for now) * linting * tts for context investigation messages * prompt tweaks * tweak intro * fix intro text tts not auto playing sometimes * consider narrator voice when assigning voice tro a character * allow director log messages to go only into the director console * linting * startup performance fixes * init time * linting * only show audio control for messagews taht can have it * always create story intent and dont override existing saves during character card load * fix history check in dynamic story line node add HasHistory node * linting * fix intro message not having speaker separation * voice library character manager * sequantial and cancelable auto assign all * linting * fix generation cancel handling * tooltips * fix auto assign voice from scene voices * polish * kokoro does not like lazy import * update info text * complete scene export / import * linting * wording * remove cruft * fix story intent generation during character card import * fix generation cancelled emit status inf loop * prompt tweak * reasoning quick toggle, reasoning token slider, tooltips * improved reasoning pattern handling * fix indirect coercion response parsing * fix streaming issue * response length instructions * more robust streaming * adjust default * adjust formatting * litning * remove debug output * director console log function calls * install cuda script updated * linting * add another step * adjust default * update dialogue examples * fix voice selection issues * what's happening here * third time's the charm? * Vite migration (#207) * add vite config * replace babel, webpack, vue-cli deps with vite, switch to esm modules, separate eslint config * change process.env to import.meta.env * update index.html for vite and move to root * update docs for vite * remove vue cli config * update example env with vite * bump frontend deps after rebase to 32.0 --------- Co-authored-by: pax-co * properly referencer data type * what's new * better indication of dialogue example supporting multiple lines, improve dialogue example display * fix potential issue with cached scene anlysis being reused when it shouldn't * fix character creation issues with player character toggle * fix issue where editing a message would sometimes lose parts of the message * fix slider ux thumb labels (vuetify update) * relock * narrative conversation format * remove planning step * linting * tweaks * don't overthink * update dialogue examples and intro * dont dictate response length instructions when data structures are expected * prompt tweaks * prompt tweaks * linting * fix edit message not handling : well * prompt tweaks * fix tests * fix manual revision when character message was generated in new narrative mode * fix issue with message editing * Docker packages relese (#204) * add CI workflow for Docker image build and MkDocs deployment * rename CI workflow from 'ci' to 'package' * refactor CI workflow: consolidate container build and documentation deployment into a single file * fix: correct indentation for permissions in CI workflow * fix: correct indentation for steps in deploy-docs job in CI workflow * build both cpu and cuda image * docs * docs * expose writing style during state reinforcement * prompt tweaks * test container build * test container image * update docker compose * docs * test-container-build * test container build * test container build * update docker build workflows * fix guidance prompt prefix not being dropped * mount tts dir * add gpt-5 * remove debug output * docs * openai auto toggle reasoning based on model selection * linting --------- Co-authored-by: pax-co <123330830+pax-co@users.noreply.github.com> Co-authored-by: pax-co Co-authored-by: Luis Alexandre Deschamps Brandão --- .github/workflows/ci.yml | 53 +- .github/workflows/test-container-build.yml | 32 + .gitignore | 12 +- Dockerfile | 11 +- docker-compose.manual.yml | 20 + docker-compose.yml | 7 +- .../advanced/change-host-and-port.md | 4 +- docs/getting-started/installation/docker.md | 17 +- docs/img/0.32.0/add-chatterbox-voice.png | Bin 0 -> 35818 bytes docs/img/0.32.0/add-elevenlabs-voice.png | Bin 0 -> 29435 bytes docs/img/0.32.0/add-f5tts-voice.png | Bin 0 -> 44462 bytes .../img/0.32.0/character-voice-assignment.png | Bin 0 -> 66269 bytes docs/img/0.32.0/chatterbox-api-settings.png | Bin 0 -> 55089 bytes docs/img/0.32.0/chatterbox-parameters.png | Bin 0 -> 18349 bytes docs/img/0.32.0/client-reasoning-2.png | Bin 0 -> 18343 bytes docs/img/0.32.0/client-reasoning.png | Bin 0 -> 76955 bytes docs/img/0.32.0/elevenlabs-api-settings.png | Bin 0 -> 62614 bytes docs/img/0.32.0/elevenlabs-copy-voice-id.png | Bin 0 -> 9817 bytes docs/img/0.32.0/f5tts-api-settings.png | Bin 0 -> 73223 bytes docs/img/0.32.0/f5tts-parameters.png | Bin 0 -> 12650 bytes docs/img/0.32.0/google-tts-api-settings.png | Bin 0 -> 64440 bytes docs/img/0.32.0/kokoro-mixer.png | Bin 0 -> 34035 bytes docs/img/0.32.0/openai-tts-api-settings.png | Bin 0 -> 62228 bytes docs/img/0.32.0/voice-agent-settings.png | Bin 0 -> 109679 bytes .../0.32.0/voice-agent-status-characters.png | Bin 0 -> 3114 bytes docs/img/0.32.0/voice-library-access.png | Bin 0 -> 9558 bytes docs/img/0.32.0/voice-library-api-status.png | Bin 0 -> 6786 bytes docs/img/0.32.0/voice-library-interface.png | Bin 0 -> 145884 bytes docs/user-guide/agents/voice/chatterbox.md | 58 + docs/user-guide/agents/voice/elevenlabs.md | 40 +- docs/user-guide/agents/voice/f5tts.md | 78 + docs/user-guide/agents/voice/google.md | 15 + docs/user-guide/agents/voice/index.md | 54 +- docs/user-guide/agents/voice/kokoro.md | 55 + docs/user-guide/agents/voice/local_tts.md | 53 - docs/user-guide/agents/voice/openai.md | 10 +- docs/user-guide/agents/voice/settings.md | 65 +- docs/user-guide/agents/voice/voice-library.md | 156 + docs/user-guide/clients/reasoning.md | 82 + docs/user-guide/clients/types/openai.md | 17 +- install-cuda.sh | 2 +- pyproject.toml | 44 +- scenes/infinity-quest/infinity-quest.json | 12 +- .../nodes/fn-sim-suite-add-character.json | 82 +- .../nodes/fn-sim-suite-set-goal.json | 132 +- .../nodes/sim-suite-process-commands.json | 41 +- .../templates/computer.jinja2 | 4 +- src/talemate/agents/base.py | 80 +- src/talemate/agents/conversation/__init__.py | 58 +- src/talemate/agents/creator/__init__.py | 2 +- .../creator/modules/create-character.json | 280 +- src/talemate/agents/director/__init__.py | 209 +- .../agents/director/character_management.py | 333 + src/talemate/agents/director/guide.py | 6 +- src/talemate/agents/director/nodes.py | 89 +- .../agents/director/websocket_handler.py | 77 +- src/talemate/agents/editor/__init__.py | 3 +- .../agents/editor/websocket_handler.py | 3 + src/talemate/agents/memory/__init__.py | 71 +- src/talemate/agents/narrator/__init__.py | 10 +- src/talemate/agents/summarize/__init__.py | 6 +- .../agents/summarize/analyze_scene.py | 38 +- src/talemate/agents/summarize/tts_utils.py | 59 + src/talemate/agents/tts.py | 670 - src/talemate/agents/tts/__init__.py | 995 ++ src/talemate/agents/tts/chatterbox.py | 317 + src/talemate/agents/tts/elevenlabs.py | 248 + src/talemate/agents/tts/f5tts.py | 436 + src/talemate/agents/tts/google.py | 319 + src/talemate/agents/tts/kokoro.py | 324 + src/talemate/agents/tts/nodes.py | 165 + src/talemate/agents/tts/openai.py | 230 + src/talemate/agents/tts/providers.py | 24 + src/talemate/agents/tts/schema.py | 201 + src/talemate/agents/tts/util.py | 111 + src/talemate/agents/tts/voice_library.py | 169 + src/talemate/agents/tts/websocket_handler.py | 674 + src/talemate/agents/visual/__init__.py | 27 +- src/talemate/agents/visual/comfyui.py | 29 +- src/talemate/agents/visual/openai_image.py | 2 +- .../agents/visual/websocket_handler.py | 2 +- src/talemate/agents/world_state/__init__.py | 3 +- src/talemate/character.py | 540 +- src/talemate/client/anthropic.py | 142 +- src/talemate/client/base.py | 659 +- src/talemate/client/cohere.py | 101 +- src/talemate/client/deepseek.py | 114 +- src/talemate/client/google.py | 160 +- src/talemate/client/groq.py | 151 +- src/talemate/client/koboldcpp.py | 34 +- src/talemate/client/lmstudio.py | 22 +- src/talemate/client/mistral.py | 138 +- src/talemate/client/model_prompts.py | 5 +- src/talemate/client/ollama.py | 95 +- src/talemate/client/openai.py | 222 +- src/talemate/client/openai_compat.py | 102 +- src/talemate/client/openrouter.py | 253 +- src/talemate/client/presets.py | 37 +- src/talemate/client/remote.py | 50 +- src/talemate/client/runpod.py | 7 +- src/talemate/client/tabbyapi.py | 98 +- src/talemate/client/textgenwebui.py | 16 +- src/talemate/commands/__init__.py | 2 - src/talemate/commands/cmd_debug_tools.py | 2 +- src/talemate/commands/cmd_rebuild_archive.py | 5 +- src/talemate/commands/cmd_save.py | 17 - src/talemate/commands/cmd_save_as.py | 17 - src/talemate/config/__init__.py | 11 + src/talemate/{config.py => config/schema.py} | 282 +- src/talemate/config/state.py | 161 + src/talemate/emit/signals.py | 3 - src/talemate/exceptions.py | 38 + src/talemate/export.py | 177 +- src/talemate/game/engine/nodes/focal.py | 15 + src/talemate/game/engine/nodes/history.py | 23 + .../modules/scene/dynamic-storyline.json | 349 +- src/talemate/game/engine/nodes/scene.py | 7 +- src/talemate/game/engine/nodes/string.py | 18 + src/talemate/game/focal/__init__.py | 18 +- src/talemate/instance.py | 189 +- src/talemate/load.py | 297 +- src/talemate/path.py | 17 + src/talemate/prompts/base.py | 45 +- .../common/narrative-patterns.jinja2 | 66 + .../templates/common/response-length.jinja2 | 17 +- .../conversation/dialogue-narrative.jinja2 | 80 + .../templates/conversation/dialogue.jinja2 | 4 +- .../creator/determine-content-context.jinja2 | 2 +- .../templates/director/cm-assign-voice.jinja2 | 59 + .../director/guide-conversation.jinja2 | 10 +- .../templates/director/guide-narration.jinja2 | 11 +- .../templates/director/scene-context.jinja2 | 1 - .../narrator/narrate-character-entry.jinja2 | 2 +- .../narrator/narrate-character-exit.jinja2 | 2 +- .../templates/narrator/narrate-query.jinja2 | 1 - .../narrator/narrate-time-passage.jinja2 | 2 +- .../narrator/narrative-direction.jinja2 | 3 +- ...analyze-scene-for-next-conversation.jinja2 | 2 - .../summarizer/markup-context-for-tts.jinja2 | 135 + ...yze-history-and-follow-instructions.jinja2 | 6 +- .../determine-content-context.jinja2 | 2 +- .../world_state/request-world-state-v2.jinja2 | 4 +- .../world_state/update-reinforcements.jinja2 | 7 +- src/talemate/scene_assets.py | 2 +- src/talemate/scene_message.py | 15 +- src/talemate/server/api.py | 45 +- src/talemate/server/assistant.py | 19 +- src/talemate/server/config.py | 55 +- src/talemate/server/devtools.py | 5 +- src/talemate/server/quick_settings.py | 9 +- src/talemate/server/run.py | 20 +- src/talemate/server/websocket_plugin.py | 21 +- src/talemate/server/websocket_server.py | 275 +- .../server/world_state_manager/__init__.py | 18 +- .../server/world_state_manager/character.py | 62 + src/talemate/tale_mate.py | 669 +- src/talemate/util/dialogue.py | 142 + src/talemate/ux/schema.py | 31 + src/talemate/version.py | 2 +- src/talemate/world_state/manager.py | 29 +- start-frontend.sh | 2 +- talemate_frontend/README.md | 2 +- talemate_frontend/babel.config.js | 5 - talemate_frontend/eslint.config.js | 23 + .../example.env.development.local | 2 +- talemate_frontend/{public => }/index.html | 6 +- talemate_frontend/package-lock.json | 11429 ++-------------- talemate_frontend/package.json | 40 +- talemate_frontend/src/components/AIAgent.vue | 64 +- talemate_frontend/src/components/AIClient.vue | 166 +- .../src/components/AgentModal.vue | 73 +- .../src/components/AudioQueue.vue | 160 +- .../src/components/CharacterMessage.vue | 19 +- .../src/components/ClientModal.vue | 48 +- .../src/components/ConfigWidgetField.vue | 161 + .../src/components/ConfigWidgetTable.vue | 132 + .../ContextInvestigationMessage.vue | 18 +- .../src/components/DebugToolPromptLog.vue | 1 + .../src/components/DebugToolPromptView.vue | 45 +- .../src/components/DirectorConsole.vue | 52 +- .../src/components/DirectorConsoleMessage.vue | 24 +- .../src/components/DirectorConsoleWidget.vue | 10 +- .../src/components/HelloWorld.vue | 149 - .../src/components/NarratorMessage.vue | 24 + .../src/components/SceneMessages.vue | 63 +- .../src/components/SceneTools.vue | 27 +- .../src/components/SceneToolsSave.vue | 78 + .../src/components/TalemateApp.vue | 99 +- .../src/components/VoiceLibrary.vue | 881 ++ .../VoiceLibraryCharacterManager.vue | 315 + .../src/components/VoiceMixer.vue | 393 + .../src/components/VoiceSelect.vue | 133 + talemate_frontend/src/components/WhatsNew.vue | 65 +- .../src/components/WorldStateManager.vue | 1 - .../WorldStateManagerCharacterActor.vue | 149 +- .../WorldStateManagerCharacterCreator.vue | 17 + .../WorldStateManagerSceneExport.vue | 75 +- .../WorldStateManagerWorldEntries.vue | 24 +- .../WorldStateManagerWorldStates.vue | 13 +- talemate_frontend/src/plugins/vuetify.js | 1 + talemate_frontend/vite.config.mjs | 38 + talemate_frontend/vue.config.js | 31 - templates/llm-prompt/std/KoboldAI.jinja2 | 1 + templates/llm-prompt/talemate/KoboldAI.jinja2 | 1 + tests/test_dialogue_cleanup.py | 277 + tests/test_graphs.py | 55 +- tts/voice/chatterbox/adam.wav | Bin 0 -> 695886 bytes tts/voice/chatterbox/bradford.wav | Bin 0 -> 672846 bytes tts/voice/chatterbox/eva.wav | Bin 0 -> 785742 bytes tts/voice/chatterbox/julia.wav | Bin 0 -> 758094 bytes tts/voice/chatterbox/lisa.wav | Bin 0 -> 792654 bytes .../put_chatterbox_voice_samples_here | 0 tts/voice/chatterbox/william.wav | Bin 0 -> 716622 bytes tts/voice/chatterbox/zoe.wav | Bin 0 -> 850254 bytes tts/voice/f5tts/adam.wav | Bin 0 -> 695886 bytes tts/voice/f5tts/bradford.wav | Bin 0 -> 672846 bytes tts/voice/f5tts/eva.wav | Bin 0 -> 785742 bytes tts/voice/f5tts/julia.wav | Bin 0 -> 758094 bytes tts/voice/f5tts/lisa.wav | Bin 0 -> 792654 bytes tts/voice/f5tts/william.wav | Bin 0 -> 716622 bytes tts/voice/f5tts/zoe.wav | Bin 0 -> 850254 bytes tts/voice/kokoro/custom_kokoro_voices_go_here | 0 uv.lock | 4078 ++++-- 223 files changed, 16882 insertions(+), 16488 deletions(-) create mode 100644 .github/workflows/test-container-build.yml create mode 100644 docker-compose.manual.yml create mode 100644 docs/img/0.32.0/add-chatterbox-voice.png create mode 100644 docs/img/0.32.0/add-elevenlabs-voice.png create mode 100644 docs/img/0.32.0/add-f5tts-voice.png create mode 100644 docs/img/0.32.0/character-voice-assignment.png create mode 100644 docs/img/0.32.0/chatterbox-api-settings.png create mode 100644 docs/img/0.32.0/chatterbox-parameters.png create mode 100644 docs/img/0.32.0/client-reasoning-2.png create mode 100644 docs/img/0.32.0/client-reasoning.png create mode 100644 docs/img/0.32.0/elevenlabs-api-settings.png create mode 100644 docs/img/0.32.0/elevenlabs-copy-voice-id.png create mode 100644 docs/img/0.32.0/f5tts-api-settings.png create mode 100644 docs/img/0.32.0/f5tts-parameters.png create mode 100644 docs/img/0.32.0/google-tts-api-settings.png create mode 100644 docs/img/0.32.0/kokoro-mixer.png create mode 100644 docs/img/0.32.0/openai-tts-api-settings.png create mode 100644 docs/img/0.32.0/voice-agent-settings.png create mode 100644 docs/img/0.32.0/voice-agent-status-characters.png create mode 100644 docs/img/0.32.0/voice-library-access.png create mode 100644 docs/img/0.32.0/voice-library-api-status.png create mode 100644 docs/img/0.32.0/voice-library-interface.png create mode 100644 docs/user-guide/agents/voice/chatterbox.md create mode 100644 docs/user-guide/agents/voice/f5tts.md create mode 100644 docs/user-guide/agents/voice/google.md create mode 100644 docs/user-guide/agents/voice/kokoro.md delete mode 100644 docs/user-guide/agents/voice/local_tts.md create mode 100644 docs/user-guide/agents/voice/voice-library.md create mode 100644 docs/user-guide/clients/reasoning.md create mode 100644 src/talemate/agents/director/character_management.py create mode 100644 src/talemate/agents/summarize/tts_utils.py delete mode 100644 src/talemate/agents/tts.py create mode 100644 src/talemate/agents/tts/__init__.py create mode 100644 src/talemate/agents/tts/chatterbox.py create mode 100644 src/talemate/agents/tts/elevenlabs.py create mode 100644 src/talemate/agents/tts/f5tts.py create mode 100644 src/talemate/agents/tts/google.py create mode 100644 src/talemate/agents/tts/kokoro.py create mode 100644 src/talemate/agents/tts/nodes.py create mode 100644 src/talemate/agents/tts/openai.py create mode 100644 src/talemate/agents/tts/providers.py create mode 100644 src/talemate/agents/tts/schema.py create mode 100644 src/talemate/agents/tts/util.py create mode 100644 src/talemate/agents/tts/voice_library.py create mode 100644 src/talemate/agents/tts/websocket_handler.py delete mode 100644 src/talemate/commands/cmd_save.py delete mode 100644 src/talemate/commands/cmd_save_as.py create mode 100644 src/talemate/config/__init__.py rename src/talemate/{config.py => config/schema.py} (67%) create mode 100644 src/talemate/config/state.py create mode 100644 src/talemate/path.py create mode 100644 src/talemate/prompts/templates/common/narrative-patterns.jinja2 create mode 100644 src/talemate/prompts/templates/conversation/dialogue-narrative.jinja2 create mode 100644 src/talemate/prompts/templates/director/cm-assign-voice.jinja2 create mode 100644 src/talemate/prompts/templates/summarizer/markup-context-for-tts.jinja2 create mode 100644 src/talemate/server/world_state_manager/character.py delete mode 100644 talemate_frontend/babel.config.js create mode 100644 talemate_frontend/eslint.config.js rename talemate_frontend/{public => }/index.html (55%) create mode 100644 talemate_frontend/src/components/ConfigWidgetField.vue create mode 100644 talemate_frontend/src/components/ConfigWidgetTable.vue delete mode 100644 talemate_frontend/src/components/HelloWorld.vue create mode 100644 talemate_frontend/src/components/SceneToolsSave.vue create mode 100644 talemate_frontend/src/components/VoiceLibrary.vue create mode 100644 talemate_frontend/src/components/VoiceLibraryCharacterManager.vue create mode 100644 talemate_frontend/src/components/VoiceMixer.vue create mode 100644 talemate_frontend/src/components/VoiceSelect.vue create mode 100644 talemate_frontend/vite.config.mjs delete mode 100644 talemate_frontend/vue.config.js create mode 100644 templates/llm-prompt/std/KoboldAI.jinja2 create mode 100644 templates/llm-prompt/talemate/KoboldAI.jinja2 create mode 100644 tts/voice/chatterbox/adam.wav create mode 100644 tts/voice/chatterbox/bradford.wav create mode 100644 tts/voice/chatterbox/eva.wav create mode 100644 tts/voice/chatterbox/julia.wav create mode 100644 tts/voice/chatterbox/lisa.wav create mode 100644 tts/voice/chatterbox/put_chatterbox_voice_samples_here create mode 100644 tts/voice/chatterbox/william.wav create mode 100644 tts/voice/chatterbox/zoe.wav create mode 100644 tts/voice/f5tts/adam.wav create mode 100644 tts/voice/f5tts/bradford.wav create mode 100644 tts/voice/f5tts/eva.wav create mode 100644 tts/voice/f5tts/julia.wav create mode 100644 tts/voice/f5tts/lisa.wav create mode 100644 tts/voice/f5tts/william.wav create mode 100644 tts/voice/f5tts/zoe.wav create mode 100644 tts/voice/kokoro/custom_kokoro_voices_go_here diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2531fe35..4b52d77c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,30 +1,57 @@ -name: ci +name: ci + on: push: branches: - - master - main - - prep-0.26.0 + - master + release: + types: [published] + permissions: contents: write + packages: write + jobs: - deploy: + container-build: + if: github.event_name == 'release' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Configure Git Credentials - run: | - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - - uses: actions/setup-python@v5 + + - name: Log in to GHCR + uses: docker/login-action@v3 with: - python-version: 3.x - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & push + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: true + tags: | + ghcr.io/${{ github.repository }}:latest + ghcr.io/${{ github.repository }}:${{ github.ref_name }} + + deploy-docs: + if: github.event_name == 'release' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure Git credentials + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + - uses: actions/setup-python@v5 + with: { python-version: '3.x' } + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - uses: actions/cache@v4 with: key: mkdocs-material-${{ env.cache_id }} path: .cache - restore-keys: | - mkdocs-material- + restore-keys: mkdocs-material- - run: pip install mkdocs-material mkdocs-awesome-pages-plugin mkdocs-glightbox - run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/.github/workflows/test-container-build.yml b/.github/workflows/test-container-build.yml new file mode 100644 index 00000000..9f63b960 --- /dev/null +++ b/.github/workflows/test-container-build.yml @@ -0,0 +1,32 @@ +name: test-container-build + +on: + push: + branches: [ 'prep-*' ] + +permissions: + contents: read + packages: write + +jobs: + container-build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build & push + uses: docker/build-push-action@v5 + with: + context: . + file: Dockerfile + push: true + # Tag with prep suffix to avoid conflicts with production + tags: | + ghcr.io/${{ github.repository }}:${{ github.ref_name }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index d2b11bf3..724ee4d5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,11 +8,20 @@ talemate_env chroma config.yaml +.cursor +.claude # uv .venv/ templates/llm-prompt/user/*.jinja2 templates/world-state/*.yaml +tts/voice/piper/*.onnx +tts/voice/piper/*.json +tts/voice/kokoro/*.pt +tts/voice/xtts2/*.wav +tts/voice/chatterbox/*.wav +tts/voice/f5tts/*.wav +tts/voice/voice-library.json scenes/ !scenes/infinity-quest-dynamic-scenario/ !scenes/infinity-quest-dynamic-scenario/assets/ @@ -21,4 +30,5 @@ scenes/ !scenes/infinity-quest/assets/ !scenes/infinity-quest/infinity-quest.json tts_voice_samples/*.wav -third-party-docs/ \ No newline at end of file +third-party-docs/ +legacy-state-reinforcements.yaml \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index b090c566..e5897210 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,18 +35,9 @@ COPY pyproject.toml uv.lock /app/ # Copy the Python source code (needed for editable install) COPY ./src /app/src -# Create virtual environment and install dependencies +# Create virtual environment and install dependencies (includes CUDA support via pyproject.toml) RUN uv sync -# Conditional PyTorch+CUDA install -ARG CUDA_AVAILABLE=false -RUN . /app/.venv/bin/activate && \ - if [ "$CUDA_AVAILABLE" = "true" ]; then \ - echo "Installing PyTorch with CUDA support..." && \ - uv pip uninstall torch torchaudio && \ - uv pip install torch~=2.7.0 torchaudio~=2.7.0 --index-url https://download.pytorch.org/whl/cu128; \ - fi - # Stage 3: Final image FROM python:3.11-slim diff --git a/docker-compose.manual.yml b/docker-compose.manual.yml new file mode 100644 index 00000000..178f2475 --- /dev/null +++ b/docker-compose.manual.yml @@ -0,0 +1,20 @@ +version: '3.8' + +services: + talemate: + build: + context: . + dockerfile: Dockerfile + ports: + - "${FRONTEND_PORT:-8080}:8080" + - "${BACKEND_PORT:-5050}:5050" + volumes: + - ./config.yaml:/app/config.yaml + - ./scenes:/app/scenes + - ./templates:/app/templates + - ./chroma:/app/chroma + - ./tts:/app/tts + environment: + - PYTHONUNBUFFERED=1 + - PYTHONPATH=/app/src:$PYTHONPATH + command: ["uv", "run", "src/talemate/server/run.py", "runserver", "--host", "0.0.0.0", "--port", "5050", "--frontend-host", "0.0.0.0", "--frontend-port", "8080"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 5ff24a95..1a8a12e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,11 +2,7 @@ version: '3.8' services: talemate: - build: - context: . - dockerfile: Dockerfile - args: - - CUDA_AVAILABLE=${CUDA_AVAILABLE:-false} + image: ghcr.io/vegu-ai/talemate:latest ports: - "${FRONTEND_PORT:-8080}:8080" - "${BACKEND_PORT:-5050}:5050" @@ -15,6 +11,7 @@ services: - ./scenes:/app/scenes - ./templates:/app/templates - ./chroma:/app/chroma + - ./tts:/app/tts environment: - PYTHONUNBUFFERED=1 - PYTHONPATH=/app/src:$PYTHONPATH diff --git a/docs/getting-started/advanced/change-host-and-port.md b/docs/getting-started/advanced/change-host-and-port.md index 64ea2e89..56b95d86 100644 --- a/docs/getting-started/advanced/change-host-and-port.md +++ b/docs/getting-started/advanced/change-host-and-port.md @@ -27,10 +27,10 @@ uv run src\talemate\server\run.py runserver --host 0.0.0.0 --port 1234 ### Letting the frontend know about the new host and port -Copy `talemate_frontend/example.env.development.local` to `talemate_frontend/.env.production.local` and edit the `VUE_APP_TALEMATE_BACKEND_WEBSOCKET_URL`. +Copy `talemate_frontend/example.env.development.local` to `talemate_frontend/.env.production.local` and edit the `VITE_TALEMATE_BACKEND_WEBSOCKET_URL`. ```env -VUE_APP_TALEMATE_BACKEND_WEBSOCKET_URL=ws://localhost:1234 +VITE_TALEMATE_BACKEND_WEBSOCKET_URL=ws://localhost:1234 ``` Next rebuild the frontend. diff --git a/docs/getting-started/installation/docker.md b/docs/getting-started/installation/docker.md index 510db31d..9e4947c6 100644 --- a/docs/getting-started/installation/docker.md +++ b/docs/getting-started/installation/docker.md @@ -1,22 +1,15 @@ -!!! example "Experimental" - Talemate through docker has not received a lot of testing from me, so please let me know if you encounter any issues. - - You can do so by creating an issue on the [:material-github: GitHub repository](https://github.com/vegu-ai/talemate) - ## Quick install instructions 1. `git clone https://github.com/vegu-ai/talemate.git` 1. `cd talemate` 1. copy config file 1. linux: `cp config.example.yaml config.yaml` - 1. windows: `copy config.example.yaml config.yaml` -1. If your host has a CUDA compatible Nvidia GPU - 1. Windows (via PowerShell): `$env:CUDA_AVAILABLE="true"; docker compose up` - 1. Linux: `CUDA_AVAILABLE=true docker compose up` -1. If your host does **NOT** have a CUDA compatible Nvidia GPU - 1. Windows: `docker compose up` - 1. Linux: `docker compose up` + 1. windows: `copy config.example.yaml config.yaml` (or just copy the file and rename it via the file explorer) +1. `docker compose up` 1. Navigate your browser to http://localhost:8080 +!!! info "Pre-built Images" + The default setup uses pre-built images from GitHub Container Registry that include CUDA support by default. To manually build the container instead, use `docker compose -f docker-compose.manual.yml up --build`. + !!! note When connecting local APIs running on the hostmachine (e.g. text-generation-webui), you need to use `host.docker.internal` as the hostname. diff --git a/docs/img/0.32.0/add-chatterbox-voice.png b/docs/img/0.32.0/add-chatterbox-voice.png new file mode 100644 index 0000000000000000000000000000000000000000..17e70fcd1dec7f338b5290f4e4d78ab09767592e GIT binary patch literal 35818 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 OU6xSmi&YShoBsvU@KxOa literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/add-f5tts-voice.png b/docs/img/0.32.0/add-f5tts-voice.png new file mode 100644 index 0000000000000000000000000000000000000000..8e47c836050b45c75562ced82baabac74775165e GIT binary patch literal 44462 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

J8t5Z@SH;2x}M2X!RSjF23S{q>47tRzx3nJ%? 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 LJjs=K>GgjAGF*w) literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/character-voice-assignment.png b/docs/img/0.32.0/character-voice-assignment.png new file mode 100644 index 0000000000000000000000000000000000000000..40975511703b225184e88305a0e710eeac0447fc GIT binary patch literal 66269 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%0Adqfu8@@0L;+M9Q*BId1HZfr>h}fF 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 literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/chatterbox-parameters.png b/docs/img/0.32.0/chatterbox-parameters.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b39769eda08ce99006447a57fbbf1f9e46043a GIT binary patch literal 18349 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 NETbY__Q*Kse*oIpYd-)0 literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/client-reasoning-2.png b/docs/img/0.32.0/client-reasoning-2.png new file mode 100644 index 0000000000000000000000000000000000000000..99a48fb2704d890591e47466d887707c173cda17 GIT binary patch literal 18343 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 xYEnPtYC>=^FEkmbRqoRP+(1S`ycb76MF_a)( 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 Z9k(dkgY*q422AizP3g)7oZ_uV{|ix?aYFzA literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/elevenlabs-api-settings.png b/docs/img/0.32.0/elevenlabs-api-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..886bfff7afc0b72da8e1757fbf960c5cdea5421b GIT binary patch literal 62614 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 MK}msfzHi?D2VdSbQUCw| literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/elevenlabs-copy-voice-id.png b/docs/img/0.32.0/elevenlabs-copy-voice-id.png new file mode 100644 index 0000000000000000000000000000000000000000..6f7b2057f897f630b155e9db7aeaa0b29abcf370 GIT binary patch literal 9817 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 F{U2mt36lT- literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/f5tts-api-settings.png b/docs/img/0.32.0/f5tts-api-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..21eb184c714d33cdd009a0502c4eed1c0b96639f GIT binary patch literal 73223 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 literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/f5tts-parameters.png b/docs/img/0.32.0/f5tts-parameters.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ae1b118c9e8b35cdfce3960c68765aa7ff0a47 GIT binary patch literal 12650 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 U(-nmN0eOV literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/openai-tts-api-settings.png b/docs/img/0.32.0/openai-tts-api-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..154724276183a7e6c0af046fbdea7b0c62b121e7 GIT binary patch literal 62228 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 KUnN_(w4#13c=i8M%Is(Q4k2k!3e;T;4 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%0D`J~;8;3TZORE$Ipax_=; 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 literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/voice-library-access.png b/docs/img/0.32.0/voice-library-access.png new file mode 100644 index 0000000000000000000000000000000000000000..7fbac88c834643ff587bf385d85b9c5b401c188c GIT binary patch literal 9558 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 literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/voice-library-api-status.png b/docs/img/0.32.0/voice-library-api-status.png new file mode 100644 index 0000000000000000000000000000000000000000..7fd6ec5e592552585e4450b1520009c00eea91c5 GIT binary patch literal 6786 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 K6f0g>2mc?Jj54SI literal 0 HcmV?d00001 diff --git a/docs/img/0.32.0/voice-library-interface.png b/docs/img/0.32.0/voice-library-interface.png new file mode 100644 index 0000000000000000000000000000000000000000..6271fa28cab6aad9abd300ac6a7c9da0b4731145 GIT binary patch literal 145884 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%0DGVpB(#+w5sjc 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 p`71{B{}0MPUNG|4|F;dwx@5oSIUXXCX5Ag|pQ4 "Copy Voice ID" for the desired voice + +![Copy Voice ID](/talemate/img/0.32.0/elevenlabs-copy-voice-id.png) + +### Creating a Voice in Talemate + +![Add ElevenLabs voice](/talemate/img/0.32.0/add-elevenlabs-voice.png) + +1. Open the Voice Library +2. Click "Add Voice" +3. Select "ElevenLabs" as the provider +4. Configure the voice: + +**Label:** Descriptive name for the voice + +**Provider ID:** Paste the ElevenLabs voice ID you copied + +**Tags:** Add descriptive tags for organization \ No newline at end of file diff --git a/docs/user-guide/agents/voice/f5tts.md b/docs/user-guide/agents/voice/f5tts.md new file mode 100644 index 00000000..f5c446ea --- /dev/null +++ b/docs/user-guide/agents/voice/f5tts.md @@ -0,0 +1,78 @@ +# F5-TTS + +Local zero shot voice cloning from .wav files. + +![F5-TTS configuration](/talemate/img/0.32.0/f5tts-api-settings.png) + +##### Device +Auto-detects best available option (GPU preferred) + +##### Model + +- F5TTS_v1_Base (default, most recent model) +- F5TTS_Base +- E2TTS_Base + +##### NFE Step + +Number of steps to generate the voice. Higher values result in more detailed voices. + +##### Chunk size + +Split text into chunks of this size. Smaller values will increase responsiveness at the cost of lost context between chunks. (Stuff like appropriate inflection, etc.). 0 = no chunking + +##### Replace exclamation marks + +If checked, exclamation marks will be replaced with periods. This is recommended for `F5TTS_v1_Base` since it seems to over exaggerate exclamation marks. + +## Adding F5-TTS Voices + +### Voice Requirements + +F5-TTS voices require: + +- Reference audio file (.wav format, 10-30 seconds) +- Clear speech with minimal background noise +- Single speaker throughout the sample +- Reference text (optional but recommended) + +### Creating a Voice + +1. Open the Voice Library +2. Click "Add Voice" +3. Select "F5-TTS" as the provider +4. Configure the voice: + +![Add F5-TTS voice](/talemate/img/0.32.0/add-f5tts-voice.png) + +**Label:** Descriptive name (e.g., "Emma - Calm Female") + +**Voice ID / Upload File** Upload a .wav file containing the **reference audio** voice sample. The uploaded reference audio will also be the voice ID. + +- Use 6-10 second samples (longer doesn't improve quality) +- Ensure clear speech with minimal background noise +- Record at natural speaking pace + +**Reference Text:** Enter the exact text spoken in the reference audio for improved quality + +- Enter exactly what is spoken in the reference audio +- Include proper punctuation and capitalization +- Improves voice cloning accuracy significantly + +**Speed:** Adjust playback speed (0.5 to 2.0, default 1.0) + +**Tags:** Add descriptive tags (gender, age, style) for organization + +**Extra voice parameters** + +There exist some optional parameters that can be set here on a per voice level. + +![F5-TTS extra voice parameters](/talemate/img/0.32.0/f5tts-parameters.png) + +##### Speed + +Allows you to adjust the speed of the voice. + +##### CFG Strength + + A higher CFG strength generally leads to more faithful reproduction of the input text, while a lower CFG strength can result in more varied or creative speech output, potentially at the cost of text-to-speech accuracy. \ No newline at end of file diff --git a/docs/user-guide/agents/voice/google.md b/docs/user-guide/agents/voice/google.md new file mode 100644 index 00000000..8718c9c9 --- /dev/null +++ b/docs/user-guide/agents/voice/google.md @@ -0,0 +1,15 @@ +# Google Gemini-TTS + +Google Gemini-TTS provides access to Google's text-to-speech service. + +## API Setup + +Google Gemini-TTS requires a Google Cloud API key. + +See the [Google Cloud API setup](/talemate/user-guide/apis/google/) for instructions on obtaining an API key. + +## Configuration + +![Google TTS settings](/talemate/img/0.32.0/google-tts-api-settings.png) + +**Model:** Select from available Google TTS models \ No newline at end of file diff --git a/docs/user-guide/agents/voice/index.md b/docs/user-guide/agents/voice/index.md index b47d002c..8e9a16f3 100644 --- a/docs/user-guide/agents/voice/index.md +++ b/docs/user-guide/agents/voice/index.md @@ -1,6 +1,26 @@ # Overview -Talemate supports Text-to-Speech (TTS) functionality, allowing users to convert text into spoken audio. This document outlines the steps required to configure TTS for Talemate using different providers, including ElevenLabs and a local TTS API. +In 0.32.0 Talemate's TTS (Text-to-Speech) agent has been completely refactored to provide advanced voice capabilities including per-character voice assignment, speaker separation, and support for multiple local and remote APIs. The voice system now includes a comprehensive voice library for managing and organizing voices across all supported providers. + +## Key Features + +- **Per-character voice assignment** - Each character can have their own unique voice +- **Speaker separation** - Automatic detection and separation of dialogue from narration +- **Voice library management** - Centralized management of all voices across providers +- **Multiple API support** - Support for both local and remote TTS providers +- **Director integration** - Automatic voice assignment for new characters + +## Supported APIs + +### Local APIs +- **Kokoro** - Fastest generation with predefined voice models and mixing +- **F5-TTS** - Fast voice cloning with occasional mispronunciations +- **Chatterbox** - High-quality voice cloning (slower generation) + +### Remote APIs +- **ElevenLabs** - Professional voice synthesis with voice cloning +- **Google Gemini-TTS** - Google's text-to-speech service +- **OpenAI** - OpenAI's TTS-1 and TTS-1-HD models ## Enable the Voice agent @@ -12,28 +32,30 @@ If your voice agent is disabled - indicated by the grey dot next to the agent - ![Agent disabled](/talemate/img/0.26.0/agent-disabled.png) ![Agent enabled](/talemate/img/0.26.0/agent-enabled.png) +!!! note "Ctrl click to toggle agent" + You can use Ctrl click to toggle the agent on and off. -!!! abstract "Next: Connect to a TTS api" - Next you need to decide which service / api to use for audio generation and configure the voice agent accordingly. +## Voice Library Management - - [OpenAI](openai.md) - - [ElevenLabs](elevenlabs.md) - - [Local TTS](local_tts.md) +Voices are managed through the Voice Library, accessible from the main application bar. The Voice Library allows you to: - You can also find more information about the various settings [here](settings.md). +- Add and organize voices from all supported providers +- Assign voices to specific characters +- Create mixed voices (Kokoro) +- Manage both global and scene-specific voice libraries -## Select a voice +See the [Voice Library Guide](voice-library.md) for detailed instructions. -![Elevenlaps voice missing](/talemate/img/0.26.0/voice-agent-no-voice-selected.png) +## Character Voice Assignment -Click on the agent to open the agent settings. +![Character voice assignment](/talemate/img/0.32.0/character-voice-assignment.png) -Then click on the `Narrator Voice` dropdown and select a voice. +Characters can have individual voices assigned through the Voice Library. When a character has a voice assigned: -![Elevenlaps voice selected](/talemate/img/0.26.0/voice-agent-select-voice.png) +1. Their dialogue will use their specific voice +2. The narrator voice is used for exposition in their messages (with speaker separation enabled) +3. If their assigned voice's API is not available, it falls back to the narrator voice -The selection is saved automatically, click anywhere outside the agent window to close it. +The Voice agent status will show all assigned character voices and their current status. -The Voice agent should now show that the voice is selected and be ready to use. - -![Elevenlabs ready](/talemate/img/0.26.0/elevenlabs-ready.png) \ No newline at end of file +![Voice agent status with characters](/talemate/img/0.32.0/voice-agent-status-characters.png) \ No newline at end of file diff --git a/docs/user-guide/agents/voice/kokoro.md b/docs/user-guide/agents/voice/kokoro.md new file mode 100644 index 00000000..df551b6d --- /dev/null +++ b/docs/user-guide/agents/voice/kokoro.md @@ -0,0 +1,55 @@ +# Kokoro + +Kokoro provides predefined voice models and voice mixing capabilities for creating custom voices. + +## Using Predefined Voices + +Kokoro comes with built-in voice models that are ready to use immediately + +Available predefined voices include various male and female voices with different characteristics. + +## Creating Mixed Voices + +Kokor allows you to mix voices together to create a new voice. + +### Voice Mixing Interface + + +To create a mixed voice: + +1. Open the Voice Library +2. Click ":material-plus: New" +3. Select "Kokoro" as the provider +4. Choose ":material-tune:Mixer" option +5. Configure the mixed voice: + +![Voice mixing interface](/talemate/img/0.32.0/kokoro-mixer.png) + + +**Label:** Descriptive name for the mixed voice + +**Base Voices:** Select 2-4 existing Kokoro voices to combine + +**Weights:** Set the influence of each voice (0.1 to 1.0) + +**Tags:** Descriptive tags for organization + +### Weight Configuration + +Each selected voice can have its weight adjusted: + +- Higher weights make that voice more prominent in the mix +- Lower weights make that voice more subtle +- Total weights need to sum to 1.0 +- Experiment with different combinations to achieve desired results + +### Saving Mixed Voices + +Once configured click "Add Voice", mixed voices are saved to your voice library and can be: + +- Assigned to characters +- Used as narrator voices + +just like any other voice. + +Saving a mixed cvoice may take a moment to complete. \ No newline at end of file diff --git a/docs/user-guide/agents/voice/local_tts.md b/docs/user-guide/agents/voice/local_tts.md deleted file mode 100644 index 1aa19723..00000000 --- a/docs/user-guide/agents/voice/local_tts.md +++ /dev/null @@ -1,53 +0,0 @@ -# Local TTS - -!!! warning - This has not been tested in a while and may not work as expected. It will likely be replaced with something different in the future. If this approach is currently broken its likely to remain so until it is replaced. - -For running a local TTS API, Talemate requires specific dependencies to be installed. - -### Windows Installation - -Run `install-local-tts.bat` to install the necessary requirements. - -### Linux Installation - -Execute the following command: - -```bash -pip install TTS -``` - -### Model and Device Configuration - -1. Choose a TTS model from the [Coqui TTS model list](https://github.com/coqui-ai/TTS). -2. Decide whether to use `cuda` or `cpu` for the device setting. -3. The first time you run TTS through the local API, it will download the specified model. Please note that this may take some time, and the download progress will be visible in the Talemate backend output. - -Example configuration snippet: - -```yaml -tts: - device: cuda # or 'cpu' - model: tts_models/multilingual/multi-dataset/xtts_v2 -``` - -### Voice Samples Configuration - -Configure voice samples by setting the `value` field to the path of a .wav file voice sample. Official samples can be downloaded from [Coqui XTTS-v2 samples](https://huggingface.co/coqui/XTTS-v2/tree/main/samples). - -Example configuration snippet: - -```yaml -tts: - voices: - - label: English Male - value: path/to/english_male.wav - - label: English Female - value: path/to/english_female.wav -``` - -## Saving the Configuration - -After configuring the `config.yaml` file, save your changes. Talemate will use the updated settings the next time it starts. - -For more detailed information on configuring Talemate, refer to the `config.py` file in the Talemate source code and the `config.example.yaml` file for a barebone configuration example. \ No newline at end of file diff --git a/docs/user-guide/agents/voice/openai.md b/docs/user-guide/agents/voice/openai.md index 817187a5..929420ed 100644 --- a/docs/user-guide/agents/voice/openai.md +++ b/docs/user-guide/agents/voice/openai.md @@ -8,16 +8,12 @@ See the [OpenAI API setup](/apis/openai.md) for instructions on how to set up th ## Settings -![Voice agent openai settings](/talemate/img/0.26.0/voice-agent-openai-settings.png) +![Voice agent openai settings](/talemate/img/0.32.0/openai-tts-api-settings.png) ##### Model Which model to use for generation. +- GPT-4o Mini TTS - TTS-1 -- TTS-1 HD - -!!! quote "OpenAI API documentation on quality" - For real-time applications, the standard tts-1 model provides the lowest latency but at a lower quality than the tts-1-hd model. Due to the way the audio is generated, tts-1 is likely to generate content that has more static in certain situations than tts-1-hd. In some cases, the audio may not have noticeable differences depending on your listening device and the individual person. - -Generally i have found that HD is fast enough for talemate, so this is the default. \ No newline at end of file +- TTS-1 HD \ No newline at end of file diff --git a/docs/user-guide/agents/voice/settings.md b/docs/user-guide/agents/voice/settings.md index 2fa330ae..615f6679 100644 --- a/docs/user-guide/agents/voice/settings.md +++ b/docs/user-guide/agents/voice/settings.md @@ -1,36 +1,65 @@ # Settings -![Voice agent settings](/talemate/img/0.26.0/voice-agent-settings.png) +![Voice agent settings](/talemate/img/0.32.0/voice-agent-settings.png) -##### API +##### Enabled APIs -The TTS API to use for voice generation. +Select which TTS APIs to enable. You can enable multiple APIs simultaneously: -- OpenAI -- ElevenLabs -- Local TTS +- **Kokoro** - Fastest generation with predefined voice models and mixing +- **F5-TTS** - Fast voice cloning with occasional mispronunciations +- **Chatterbox** - High-quality voice cloning (slower generation) +- **ElevenLabs** - Professional voice synthesis with voice cloning +- **Google Gemini-TTS** - Google's text-to-speech service +- **OpenAI** - OpenAI's TTS-1 and TTS-1-HD models + +!!! note "Multi-API Support" + You can enable multiple APIs and assign different voices from different providers to different characters. The system will automatically route voice generation to the appropriate API based on the voice assignment. ##### Narrator Voice -The voice to use for narration. Each API will come with its own set of voices. +The default voice used for narration and as a fallback for characters without assigned voices. -![Narrator voice](/talemate/img/0.26.0/voice-agent-select-voice.png) +The dropdown shows all available voices from all enabled APIs, with the format: "Voice Name (Provider)" -!!! note "Local TTS" - For local TTS, you will have to provide voice samples yourself. See [Local TTS Instructions](local_tts.md) for more information. +!!! info "Voice Management" + Voices are managed through the Voice Library, accessible from the main application bar. Adding, removing, or modifying voices should be done through the Voice Library interface. -##### Generate for player +##### Speaker Separation -Whether to generate voice for the player. If enabled, whenever the player speaks, the voice agent will generate audio for them. +Controls how dialogue is separated from exposition in messages: -##### Generate for NPCs +- **No separation** - Character messages use character voice entirely, narrator messages use narrator voice +- **Simple** - Basic separation of dialogue from exposition using punctuation analysis, with exposition being read by the narrator voice +- **Mixed** - Enables AI assisted separation for narrator messages and simple separation for character messages +- **AI assisted** - AI assisted separation for both narrator and character messages -Whether to generate voice for NPCs. If enabled, whenever a non player character speaks, the voice agent will generate audio for them. +!!! warning "AI Assisted Performance" + AI-assisted speaker separation sends additional prompts to your LLM, which may impact response time and API costs. -##### Generate for narration +##### Auto-generate for player -Whether to generate voice for narration. If enabled, whenever the narrator speaks, the voice agent will generate audio for them. +Generate voice automatically for player messages -##### Split generation +##### Auto-generate for AI characters -If enabled, the voice agent will generate audio in chunks, allowing for faster generation. This does however cause it lose context between chunks, and inflection may not be as good. \ No newline at end of file +Generate voice automatically for NPC/AI character messages + +##### Auto-generate for narration + +Generate voice automatically for narrator messages + +##### Auto-generate for context investigation + +Generate voice automatically for context investigation messages + +## Advanced Settings + +Advanced settings are configured per-API and can be found in the respective API configuration sections: + +- **Chunk size** - Maximum text length per generation request +- **Model selection** - Choose specific models for each API +- **Voice parameters** - Provider-specific voice settings + +!!! tip "Performance Optimization" + Each API has different optimal chunk sizes and parameters. The system automatically handles chunking and queuing for optimal performance across all enabled APIs. \ No newline at end of file diff --git a/docs/user-guide/agents/voice/voice-library.md b/docs/user-guide/agents/voice/voice-library.md new file mode 100644 index 00000000..6fc40b34 --- /dev/null +++ b/docs/user-guide/agents/voice/voice-library.md @@ -0,0 +1,156 @@ +# Voice Library + +The Voice Library is the central hub for managing all voices across all TTS providers in Talemate. It provides a unified interface for organizing, creating, and assigning voices to characters. + +## Accessing the Voice Library + +The Voice Library can be accessed from the main application bar at the top of the Talemate interface. + +![Voice Library access](/talemate/img/0.32.0/voice-library-access.png) + +Click the voice icon to open the Voice Library dialog. + +!!! note "Voice agent needs to be enabled" + The Voice agent needs to be enabled for the voice library to be available. + +## Voice Library Interface + +![Voice Library interface](/talemate/img/0.32.0/voice-library-interface.png) + +The Voice Library interface consists of: + +### Scope Tabs + +- **Global** - Voices available across all scenes +- **Scene** - Voices specific to the current scene (only visible when a scene is loaded) +- **Characters** - Character voice assignments for the current scene (only visible when a scene is loaded) + +### API Status + +The toolbar shows the status of all TTS APIs: + +- **Green** - API is enabled and ready +- **Orange** - API is enabled but not configured +- **Red** - API has configuration issues +- **Gray** - API is disabled + +![API status](/talemate/img/0.32.0/voice-library-api-status.png) + +## Managing Voices + +### Global Voice Library + +The global voice library contains voices that are available across all scenes. These include: + +- Default voices provided by each TTS provider +- Custom voices you've added + +#### Adding New Voices + +To add a new voice: + +1. Click the "+ New" button +2. Select the TTS provider +3. Configure the voice parameters: + - **Label** - Display name for the voice + - **Provider ID** - Provider-specific identifier + - **Tags** - Free-form descriptive tags you define (gender, age, style, etc.) + - **Parameters** - Provider-specific settings + +Check the provider specific documentation for more information on how to configure the voice. + +#### Voice Types by Provider + +**F5-TTS & Chatterbox:** + +- Upload .wav reference files for voice cloning +- Specify reference text for better quality +- Adjust speed and other parameters + +**Kokoro:** + +- Select from predefined voice models +- Create mixed voices by combining multiple models +- Adjust voice mixing weights + +**ElevenLabs:** + +- Select from available ElevenLabs voices +- Configure voice settings and stability +- Use custom cloned voices from your ElevenLabs account + +**OpenAI:** + +- Choose from available OpenAI voice models +- Configure model (GPT-4o Mini TTS, TTS-1, TTS-1-HD) + +**Google Gemini-TTS:** + +- Select from Google's voice models +- Configure language and gender settings + +### Scene Voice Library + +Scene-specific voices are only available within the current scene. This is useful for: + +- Scene-specific characters +- Temporary voice experiments +- Custom voices for specific scenarios + +Scene voices are saved with the scene and will be available when the scene is loaded. + +## Character Voice Assignment + +### Automatic Assignment + +The Director agent can automatically assign voices to new characters based on: + +- Character tags and attributes +- Voice tags matching character personality +- Available voices in the voice library + +This feature can be enabled in the Director agent settings. + +### Manual Assignment + +![Character voice assignment](/talemate/img/0.32.0/character-voice-assignment.png) + +To manually assign a voice to a character: + +1. Go to the "Characters" tab in the Voice Library +2. Find the character in the list +3. Click the voice dropdown for that character +4. Select a voice from the available options +5. The assignment is saved automatically + +### Character Voice Status + +The character list shows: + +- **Character name** +- **Currently assigned voice** (if any) +- **Voice status** - whether the voice's API is available +- **Quick assignment controls** + +## Voice Tags and Organization + +### Tagging System + +Voices can be tagged with any descriptive attributes you choose. Tags are completely free-form and user-defined. Common examples include: + +- **Gender**: male, female, neutral +- **Age**: young, mature, elderly +- **Style**: calm, energetic, dramatic, mysterious +- **Quality**: deep, high, raspy, smooth +- **Character types**: narrator, villain, hero, comic relief +- **Custom tags**: You can create any tags that help you organize your voices + +### Filtering and Search + +Use the search bar to filter voices by: +- Voice label/name +- Provider +- Tags +- Character assignments + +This makes it easy to find the right voice for specific characters or situations. \ No newline at end of file diff --git a/docs/user-guide/clients/reasoning.md b/docs/user-guide/clients/reasoning.md new file mode 100644 index 00000000..6b70496f --- /dev/null +++ b/docs/user-guide/clients/reasoning.md @@ -0,0 +1,82 @@ +# Reasoning Model Support + +Talemate supports reasoning models that can perform step-by-step thinking before generating their final response. This feature allows models to work through complex problems internally before providing an answer. + +## Enabling Reasoning Support + +To enable reasoning support for a client: + +1. Open the **Clients** dialog from the main toolbar +2. Select the client you want to configure +3. Navigate to the **Reasoning** tab in the client configuration + +![Client reasoning configuration](/talemate/img/0.32.0/client-reasoning-2.png) + +4. Check the **Enable Reasoning** checkbox + +## Configuring Reasoning Tokens + +Once reasoning is enabled, you can configure the **Reasoning Tokens** setting using the slider: + +![Reasoning tokens configuration](/talemate/img/0.32.0/client-reasoning.png) + +### Recommended Token Amounts + +**For local reasoning models:** Use a high token allocation (recommended: 4096 tokens) to give the model sufficient space for complex reasoning. + +**For remote APIs:** Start with lower amounts (512-1024 tokens) and adjust based on your needs and token costs. + +### Token Allocation Behavior + +The behavior of the reasoning tokens setting depends on your API provider: + +**For APIs that support direct reasoning token specification:** + +- The specified tokens will be allocated specifically for reasoning +- The model will use these tokens for internal thinking before generating the response + +**For APIs that do NOT support reasoning token specification:** + +- The tokens are added as extra allowance to the response token limit for ALL requests +- This may lead to more verbose responses than usual since Talemate normally uses response token limits to control verbosity + +!!! warning "Increased Verbosity" + For providers without direct reasoning token support, enabling reasoning may result in more verbose responses since the extra tokens are added to all requests. + +## Response Pattern Configuration + +When reasoning is enabled, you may need to configure a **Pattern to strip from the response** to remove the thinking process from the final output. + +### Default Patterns + +Talemate provides quick-access buttons for common reasoning patterns: + +- **Default** - Uses the built-in pattern: `.*?` +- **`.*?◁/think▷`** - For models using arrow-style thinking delimiters +- **`.*?`** - For models using XML-style think tags + +### Custom Patterns + +You can also specify a custom regular expression pattern that matches your model's reasoning format. This pattern will be used to strip the thinking tokens from the response before displaying it to the user. + +## Model Compatibility + +Not all models support reasoning. This feature works best with: + +- Models specifically trained for chain-of-thought reasoning +- Models that support structured thinking patterns +- APIs that provide reasoning token specification + +## Important Notes + +- **Coercion Disabled**: When reasoning is enabled, LLM coercion (pre-filling responses) is automatically disabled since reasoning models need to generate their complete thought process +- **Response Time**: Reasoning models may take longer to respond as they work through their thinking process + +## Troubleshooting + +### Pattern Not Working +If the reasoning pattern isn't properly stripping the thinking process: + +1. Check your model's actual reasoning output format +2. Adjust the regular expression pattern to match your model's specific format +3. Test with the default pattern first to see if it works \ No newline at end of file diff --git a/docs/user-guide/clients/types/openai.md b/docs/user-guide/clients/types/openai.md index a877a7ed..244e773e 100644 --- a/docs/user-guide/clients/types/openai.md +++ b/docs/user-guide/clients/types/openai.md @@ -35,4 +35,19 @@ A unique name for the client that makes sense to you. Which model to use. Currently defaults to `gpt-4o`. !!! note "Talemate lags behind OpenAI" - When OpenAI adds a new model, it currently requires a Talemate update to add it to the list of available models. We are working on making this more dynamic. \ No newline at end of file + When OpenAI adds a new model, it currently requires a Talemate update to add it to the list of available models. We are working on making this more dynamic. + +##### Reasoning models (o1, o3, gpt-5) + +!!! important "Enable reasoning and allocate tokens" + The `o1`, `o3`, and `gpt-5` families are reasoning models. They always perform internal thinking before producing the final answer. To use them effectively in Talemate: + + - Enable the **Reasoning** option in the client configuration. + - Set **Reasoning Tokens** to a sufficiently high value to make room for the model's thinking process. + + A good starting range is 512–1024 tokens. Increase if your tasks are complex. Without enabling reasoning and allocating tokens, these models may return minimal or empty visible content because the token budget is consumed by internal reasoning. + + See the detailed guide: [Reasoning Model Support](/talemate/user-guide/clients/reasoning/). + +!!! tip "Getting empty responses?" + If these models return empty or very short answers, it usually means the reasoning budget was exhausted. Increase **Reasoning Tokens** and try again. \ No newline at end of file diff --git a/install-cuda.sh b/install-cuda.sh index 89285a94..7addc33f 100755 --- a/install-cuda.sh +++ b/install-cuda.sh @@ -4,4 +4,4 @@ uv pip uninstall torch torchaudio # install torch and torchaudio with CUDA support -uv pip install torch~=2.7.0 torchaudio~=2.7.0 --index-url https://download.pytorch.org/whl/cu128 \ No newline at end of file +uv pip install torch~=2.7.1 torchaudio~=2.7.1 --index-url https://download.pytorch.org/whl/cu128 \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index be67b0a5..8b074992 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,12 @@ [project] name = "talemate" -version = "0.31.0" +version = "0.32.0" description = "AI-backed roleplay and narrative tools" authors = [{name = "VeguAITools"}] license = {text = "GNU Affero General Public License v3.0"} -requires-python = ">=3.10,<3.14" +requires-python = ">=3.11,<3.14" dependencies = [ + "pip", "astroid>=2.8", "jedi>=0.18", "black", @@ -50,11 +51,20 @@ dependencies = [ # ChromaDB "chromadb>=1.0.12", "InstructorEmbedding @ https://github.com/vegu-ai/instructor-embedding/archive/refs/heads/202506-fixes.zip", - "torch>=2.7.0", - "torchaudio>=2.7.0", - # locked for instructor embeddings - #sentence-transformers==2.2.2 + "torch>=2.7.1", + "torchaudio>=2.7.1", "sentence_transformers>=2.7.0", + # TTS + "elevenlabs>=2.7.1", + # Local TTS + # Chatterbox TTS + #"chatterbox-tts @ https://github.com/rsxdalv/chatterbox/archive/refs/heads/fast.zip", + "chatterbox-tts==0.1.2", + # kokoro TTS + "kokoro>=0.9.4", + "soundfile>=0.13.1", + # F5-TTS + "f5-tts>=1.1.7", ] [project.optional-dependencies] @@ -105,3 +115,25 @@ force_grid_wrap = 0 use_parentheses = true ensure_newline_before_comments = true line_length = 88 + +[tool.uv] +override-dependencies = [ + # chatterbox wants torch 2.6.0, but is confirmed working with 2.7.1 + "torchaudio>=2.7.1", + "torch>=2.7.1", + "numpy>=2", + "pydantic>=2.11", +] + +[tool.uv.sources] +torch = [ + { index = "pytorch-cu128" }, +] +torchaudio = [ + { index = "pytorch-cu128" }, +] + +[[tool.uv.index]] +name = "pytorch-cu128" +url = "https://download.pytorch.org/whl/cu128" +explicit = true \ No newline at end of file diff --git a/scenes/infinity-quest/infinity-quest.json b/scenes/infinity-quest/infinity-quest.json index 6bc4f5ec..afedb58b 100644 --- a/scenes/infinity-quest/infinity-quest.json +++ b/scenes/infinity-quest/infinity-quest.json @@ -1,6 +1,6 @@ { "description": "Captain Elmer Farstield and his trusty first officer, Kaira, embark upon a daring mission into uncharted space. Their small but mighty exploration vessel, the Starlight Nomad, is equipped with state-of-the-art technology and crewed by an elite team of scientists, engineers, and pilots. Together they brave the vast cosmos seeking answers to humanity's most pressing questions about life beyond our solar system.", - "intro": "You awaken aboard your ship, the Starlight Nomad, surrounded by darkness. A soft hum resonates throughout the vessel indicating its systems are online. Your mind struggles to recall what brought you here - where 'here' actually is. You remember nothing more than flashes of images; swirling nebulae, foreign constellations, alien life forms... Then there was a bright light followed by this endless void.\n\nGingerly, you make your way through the dimly lit corridors of the ship. It seems smaller than you expected given the magnitude of the mission ahead. However, each room reveals intricate technology designed specifically for long-term space travel and exploration. There appears to be no other living soul besides yourself. An eerie silence fills every corner.", + "intro": "Elmer awoke aboard his ship, the Starlight Nomad, surrounded by darkness. A soft hum resonated throughout the vessel indicating its systems were online. His mind struggled to recall what had brought him here - where here actually was. He remembered nothing more than flashes of images; swirling nebulae, foreign constellations, alien life forms... Then there had been a bright light followed by this endless void.\n\nGingerly, he made his way through the dimly lit corridors of the ship. It seemed smaller than he had expected given the magnitude of the mission ahead. However, each room revealed intricate technology designed specifically for long-term space travel and exploration. There appeared to be no other living soul besides himself. An eerie silence filled every corner.", "name": "Infinity Quest", "history": [], "environment": "scene", @@ -90,11 +90,11 @@ "gender": "female", "color": "red", "example_dialogue": [ - "Kaira: \"Yes Captain, I believe that is the best course of action\" She nods slightly, as if to punctuate her approval of the decision*", - "Kaira: \"This device appears to have multiple functions, Captain. Allow me to analyze its capabilities and determine if it could be useful in our exploration efforts.\"", - "Kaira: \"Captain, it appears that this newly discovered planet harbors an ancient civilization whose technological advancements rival those found back home on Altrusia!\" Excitement bubbles beneath her calm exterior as she shares the news", - "Kaira: \"Captain, I understand why you would want us to pursue this course of action based on our current data, but I cannot shake the feeling that there might be unforeseen consequences if we proceed without further investigation into potential hazards.\"", - "Kaira: \"I often find myself wondering what it would have been like if I had never left my home world... But then again, perhaps it was fate that led me here, onto this ship bound for destinations unknown...\"" + "Kaira: \"Yes Captain, I believe that is the best course of action.\" Kaira glanced at the navigation display, then back at him with a slight nod. \"The numbers check out on my end too. If we adjust our trajectory by 2.7 degrees and increase thrust by fifteen percent, we should reach the nebula's outer edge within six hours.\" Her violet fingers moved efficiently across the controls as she pulled up the gravitational readings.", + "Kaira: The scanner hummed as it analyzed the alien artifact. Kaira knelt beside the strange object, frowning in concentration at the shifting symbols on its warm metal surface. \"This device appears to have multiple functions, Captain,\" she said, adjusting her scanner's settings. \"Give me a few minutes to run a full analysis and I'll know what we're dealing with. The material composition is fascinating - it's responding to our ship's systems in ways that shouldn't be possible.\"", + "Kaira: \"Captain, it appears that this newly discovered planet harbors an ancient civilization whose technological advancements rival those found back home on Altrusia!\" The excitement in her voice was unmistakable as Kaira looked up from her console. \"These readings are incredible - I've never seen anything like this before. There are structures beneath the surface that predate our oldest sites by millions of years.\" She paused, processing the implications. \"If these readings are accurate, we may have found something truly significant.\"", + "Kaira: Something felt off about the proposed course of action. Kaira moved from her station to stand beside the Captain, organizing her thoughts carefully. \"Captain, I understand why you would want us to pursue this based on our current data,\" she began respectfully, clasping her hands behind her back. \"But something feels wrong about this. The quantum signatures have subtle variations that remind me of Hegemony cloaking technology. Maybe we should run a few more scans before we commit to a full approach.\"", + "Kaira: \"I often find myself wondering what it would have been like if I had never left my home world,\" she said softly, not turning from the observation deck viewport as footsteps approached. The stars wheeled slowly past in their eternal dance. \"Sometimes I dream of Altrusia's crystal gardens, the way our twin suns would set over the mountains.\" Kaira finally turned, her expression thoughtful. \"Then again, I suppose I was always meant to end up out here somehow. Perhaps this journey is exactly where I'm supposed to be.\"" ], "history_events": [], "is_player": false, diff --git a/scenes/simulation-suite-v2/nodes/fn-sim-suite-add-character.json b/scenes/simulation-suite-v2/nodes/fn-sim-suite-add-character.json index c9c2f109..88f800c9 100644 --- a/scenes/simulation-suite-v2/nodes/fn-sim-suite-add-character.json +++ b/scenes/simulation-suite-v2/nodes/fn-sim-suite-add-character.json @@ -494,34 +494,6 @@ "registry": "state/GetState", "base_type": "core/Node" }, - "8a050403-5c69-46f7-abe2-f65db4553942": { - "title": "TRUE", - "id": "8a050403-5c69-46f7-abe2-f65db4553942", - "properties": { - "value": true - }, - "x": 460, - "y": 670, - "width": 210, - "height": 58, - "collapsed": true, - "inherited": false, - "registry": "core/MakeBool", - "base_type": "core/Node" - }, - "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c": { - "title": "Create Character", - "id": "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c", - "properties": {}, - "x": 580, - "y": 550, - "width": 245, - "height": 186, - "collapsed": false, - "inherited": false, - "registry": "agents/creator/CreateCharacter", - "base_type": "core/Graph" - }, "970f12d0-330e-41b3-b025-9a53bcf2fc6f": { "title": "SET - created_character", "id": "970f12d0-330e-41b3-b025-9a53bcf2fc6f", @@ -628,6 +600,34 @@ "inherited": false, "registry": "data/string/MakeText", "base_type": "core/Node" + }, + "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c": { + "title": "Create Character", + "id": "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c", + "properties": {}, + "x": 580, + "y": 550, + "width": 245, + "height": 206, + "collapsed": false, + "inherited": false, + "registry": "agents/creator/CreateCharacter", + "base_type": "core/Graph" + }, + "8a050403-5c69-46f7-abe2-f65db4553942": { + "title": "TRUE", + "id": "8a050403-5c69-46f7-abe2-f65db4553942", + "properties": { + "value": true + }, + "x": 400, + "y": 720, + "width": 210, + "height": 58, + "collapsed": true, + "inherited": false, + "registry": "core/MakeBool", + "base_type": "core/Node" } }, "edges": { @@ -714,17 +714,6 @@ "fd4cd318-121b-47de-84a0-b1ab62c5601b.value": [ "41c0c2cd-d39b-4528-a5fe-459094393ba3.list" ], - "8a050403-5c69-46f7-abe2-f65db4553942.value": [ - "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.generate", - "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.generate_attributes", - "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.is_active" - ], - "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.state": [ - "90610e90-3d00-4b4b-96de-1f6aa3f4f795.state" - ], - "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.character": [ - "970f12d0-330e-41b3-b025-9a53bcf2fc6f.value" - ], "a2457116-35cb-4571-ba1a-cbf63851544e.value": [ "a9f17ddc-fc8f-4257-8e32-45ac111fd50d.state", "a9f17ddc-fc8f-4257-8e32-45ac111fd50d.character", @@ -738,6 +727,18 @@ ], "5041f12d-507f-4f5d-a26f-048625974602.value": [ "b50e23d4-b456-4385-b80e-c2b6884c7855.template" + ], + "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.state": [ + "90610e90-3d00-4b4b-96de-1f6aa3f4f795.state" + ], + "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.character": [ + "970f12d0-330e-41b3-b025-9a53bcf2fc6f.value" + ], + "8a050403-5c69-46f7-abe2-f65db4553942.value": [ + "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.generate_attributes", + "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.is_active", + "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.generate", + "72943c1c-d2a1-40b4-bb28-8bcc5f02aa5c.assign_voice" ] }, "groups": [ @@ -808,13 +809,14 @@ "inputs": [], "outputs": [ { - "id": "f78fa84b-8b6f-4c8a-83c0-754537bb9060", + "id": "92423e0a-89d8-4ad8-a42d-fdcba75b31d1", "name": "fn", "optional": false, "group": null, "socket_type": "function" } ], + "module_properties": {}, "style": { "title_color": "#573a2e", "node_color": "#392f2c", diff --git a/scenes/simulation-suite-v2/nodes/fn-sim-suite-set-goal.json b/scenes/simulation-suite-v2/nodes/fn-sim-suite-set-goal.json index 276fba6e..b0076e56 100644 --- a/scenes/simulation-suite-v2/nodes/fn-sim-suite-set-goal.json +++ b/scenes/simulation-suite-v2/nodes/fn-sim-suite-set-goal.json @@ -116,8 +116,8 @@ "context_aware": true, "history_aware": true }, - "x": 568, - "y": 861, + "x": 372, + "y": 857, "width": 249, "height": 406, "collapsed": false, @@ -130,7 +130,7 @@ "id": "a8110d74-0fb5-4601-b883-6c63ceaa9d31", "properties": {}, "x": 24, - "y": 2084, + "y": 2088, "width": 140, "height": 106, "collapsed": false, @@ -145,7 +145,7 @@ "attribute": "title" }, "x": 213, - "y": 2094, + "y": 2098, "width": 210, "height": 98, "collapsed": false, @@ -160,7 +160,7 @@ "value": "The Simulation Suite" }, "x": 213, - "y": 2284, + "y": 2288, "width": 210, "height": 58, "collapsed": false, @@ -177,7 +177,7 @@ "case_sensitive": true }, "x": 523, - "y": 2184, + "y": 2188, "width": 210, "height": 126, "collapsed": false, @@ -192,7 +192,7 @@ "pass_through": true }, "x": 774, - "y": 2185, + "y": 2189, "width": 210, "height": 78, "collapsed": false, @@ -207,7 +207,7 @@ "attribute": "0" }, "x": 1629, - "y": 2411, + "y": 2415, "width": 210, "height": 98, "collapsed": false, @@ -222,7 +222,7 @@ "attribute": "title" }, "x": 2189, - "y": 2321, + "y": 2325, "width": 210, "height": 98, "collapsed": false, @@ -237,7 +237,7 @@ "stage": 5 }, "x": 2459, - "y": 2331, + "y": 2335, "width": 210, "height": 118, "collapsed": true, @@ -250,7 +250,7 @@ "id": "8208d05c-1822-4f4a-ba75-cfd18d2de8ca", "properties": {}, "x": 1989, - "y": 2331, + "y": 2335, "width": 140, "height": 106, "collapsed": true, @@ -266,7 +266,7 @@ "chars": "\"'*" }, "x": 1899, - "y": 2411, + "y": 2415, "width": 210, "height": 102, "collapsed": false, @@ -282,7 +282,7 @@ "max_splits": 1 }, "x": 1359, - "y": 2411, + "y": 2415, "width": 210, "height": 102, "collapsed": false, @@ -304,7 +304,7 @@ "history_aware": true }, "x": 1014, - "y": 2185, + "y": 2189, "width": 276, "height": 406, "collapsed": false, @@ -376,8 +376,8 @@ "name": "arg_goal", "scope": "local" }, - "x": 228, - "y": 1041, + "x": 32, + "y": 1037, "width": 256, "height": 122, "collapsed": false, @@ -393,7 +393,7 @@ "max_scene_types": 2 }, "x": 671, - "y": 1490, + "y": 1494, "width": 210, "height": 122, "collapsed": false, @@ -409,7 +409,7 @@ "scope": "local" }, "x": 30, - "y": 1551, + "y": 1555, "width": 256, "height": 122, "collapsed": false, @@ -448,37 +448,6 @@ "registry": "state/GetState", "base_type": "core/Node" }, - "59d31050-f61d-4798-9790-e22d34ecbd4b": { - "title": "GET local.auto_direct_enabled", - "id": "59d31050-f61d-4798-9790-e22d34ecbd4b", - "properties": { - "name": "auto_direct_enabled", - "scope": "local" - }, - "x": 20, - "y": 850, - "width": 256, - "height": 122, - "collapsed": false, - "inherited": false, - "registry": "state/GetState", - "base_type": "core/Node" - }, - "e8f19a05-43fe-4e4a-9cc4-bec0a29779d8": { - "title": "Switch", - "id": "e8f19a05-43fe-4e4a-9cc4-bec0a29779d8", - "properties": { - "pass_through": true - }, - "x": 310, - "y": 870, - "width": 210, - "height": 78, - "collapsed": false, - "inherited": false, - "registry": "core/Switch", - "base_type": "core/Node" - }, "b03fa942-c48e-4c04-b9ae-a009a7e0f947": { "title": "GET local.auto_direct_enabled", "id": "b03fa942-c48e-4c04-b9ae-a009a7e0f947", @@ -487,7 +456,7 @@ "scope": "local" }, "x": 24, - "y": 1367, + "y": 1371, "width": 256, "height": 122, "collapsed": false, @@ -502,7 +471,7 @@ "pass_through": true }, "x": 360, - "y": 1390, + "y": 1394, "width": 210, "height": 78, "collapsed": false, @@ -518,7 +487,7 @@ "scope": "local" }, "x": 30, - "y": 1821, + "y": 1826, "width": 256, "height": 122, "collapsed": false, @@ -533,7 +502,7 @@ "stage": 4 }, "x": 1100, - "y": 1870, + "y": 1875, "width": 210, "height": 118, "collapsed": true, @@ -546,7 +515,7 @@ "id": "9db37d1e-3cf8-49bd-bdc5-8663494e5657", "properties": {}, "x": 670, - "y": 1840, + "y": 1845, "width": 226, "height": 62, "collapsed": false, @@ -561,7 +530,7 @@ "stage": 3 }, "x": 1080, - "y": 1520, + "y": 1524, "width": 210, "height": 118, "collapsed": true, @@ -576,7 +545,7 @@ "pass_through": true }, "x": 370, - "y": 1840, + "y": 1845, "width": 210, "height": 78, "collapsed": false, @@ -590,8 +559,8 @@ "properties": { "stage": 2 }, - "x": 1280, - "y": 890, + "x": 1084, + "y": 886, "width": 210, "height": 118, "collapsed": true, @@ -599,14 +568,29 @@ "registry": "core/Stage", "base_type": "core/Node" }, + "5559196c-f6b1-4223-8e13-2bf64e3cfef0": { + "title": "true", + "id": "5559196c-f6b1-4223-8e13-2bf64e3cfef0", + "properties": { + "value": true + }, + "x": 24, + "y": 876, + "width": 210, + "height": 58, + "collapsed": false, + "inherited": false, + "registry": "core/MakeBool", + "base_type": "core/Node" + }, "6ef94917-f9b1-4c18-af15-617430e50cfe": { "title": "Set Scene Intent", "id": "6ef94917-f9b1-4c18-af15-617430e50cfe", "properties": { "intent": "" }, - "x": 930, - "y": 860, + "x": 731, + "y": 853, "width": 210, "height": 78, "collapsed": false, @@ -693,14 +677,8 @@ "e4cd1391-daed-4951-a6c6-438d993c07a9.state" ], "c66bdaeb-4166-4835-9415-943af547c926.value": [ - "24ac670b-4648-4915-9dbb-b6bf35ee6d80.description", - "24ac670b-4648-4915-9dbb-b6bf35ee6d80.state" - ], - "59d31050-f61d-4798-9790-e22d34ecbd4b.value": [ - "e8f19a05-43fe-4e4a-9cc4-bec0a29779d8.value" - ], - "e8f19a05-43fe-4e4a-9cc4-bec0a29779d8.yes": [ - "bb43a68e-bdf6-4b02-9cc0-102742b14f5d.state" + "24ac670b-4648-4915-9dbb-b6bf35ee6d80.state", + "24ac670b-4648-4915-9dbb-b6bf35ee6d80.description" ], "b03fa942-c48e-4c04-b9ae-a009a7e0f947.value": [ "8ad7c42c-110e-46ae-b649-4a1e6d055e25.value" @@ -717,6 +695,9 @@ "6a8762c4-16cf-4e8c-9d10-8af7597c4097.yes": [ "9db37d1e-3cf8-49bd-bdc5-8663494e5657.state" ], + "5559196c-f6b1-4223-8e13-2bf64e3cfef0.value": [ + "bb43a68e-bdf6-4b02-9cc0-102742b14f5d.state" + ], "6ef94917-f9b1-4c18-af15-617430e50cfe.state": [ "f4cd34d9-0628-4145-a3da-ec1215cd356c.state" ] @@ -745,7 +726,7 @@ { "title": "Generate Scene Types", "x": -1, - "y": 1287, + "y": 1290, "width": 1298, "height": 408, "color": "#8AA", @@ -756,8 +737,8 @@ "title": "Set story intention", "x": -1, "y": 773, - "width": 1539, - "height": 512, + "width": 1320, + "height": 514, "color": "#8AA", "font_size": 24, "inherited": false @@ -765,7 +746,7 @@ { "title": "Evaluate Scene Intent", "x": -1, - "y": 1697, + "y": 1701, "width": 1293, "height": 302, "color": "#8AA", @@ -775,7 +756,7 @@ { "title": "Set title", "x": -1, - "y": 2003, + "y": 2006, "width": 2618, "height": 637, "color": "#8AA", @@ -794,7 +775,7 @@ { "text": "Some times the AI will produce more text after the title, we only care about the title on the first line.", "x": 1359, - "y": 2311, + "y": 2315, "width": 471, "inherited": false } @@ -804,13 +785,14 @@ "inputs": [], "outputs": [ { - "id": "dede1a38-2107-4475-9db5-358c09cb0d12", + "id": "5c8dee64-5832-40ba-b1e2-2a411d913cc7", "name": "fn", "optional": false, "group": null, "socket_type": "function" } ], + "module_properties": {}, "style": { "title_color": "#573a2e", "node_color": "#392f2c", diff --git a/scenes/simulation-suite-v2/nodes/sim-suite-process-commands.json b/scenes/simulation-suite-v2/nodes/sim-suite-process-commands.json index dd82bc7f..cc309f91 100644 --- a/scenes/simulation-suite-v2/nodes/sim-suite-process-commands.json +++ b/scenes/simulation-suite-v2/nodes/sim-suite-process-commands.json @@ -666,23 +666,6 @@ "registry": "data/ListAppend", "base_type": "core/Node" }, - "4eb36f21-1020-4609-85e5-d16b42019c66": { - "title": "AI Function Calling", - "id": "4eb36f21-1020-4609-85e5-d16b42019c66", - "properties": { - "template": "computer", - "max_calls": 5, - "retries": 1 - }, - "x": 1000, - "y": 2581, - "width": 212, - "height": 206, - "collapsed": false, - "inherited": false, - "registry": "focal/Focal", - "base_type": "core/Node" - }, "3da498c0-55de-4ec3-9943-6486279b9826": { "title": "GET scene loop.user_message", "id": "3da498c0-55de-4ec3-9943-6486279b9826", @@ -1167,6 +1150,24 @@ "inherited": false, "registry": "data/MakeList", "base_type": "core/Node" + }, + "4eb36f21-1020-4609-85e5-d16b42019c66": { + "title": "AI Function Calling", + "id": "4eb36f21-1020-4609-85e5-d16b42019c66", + "properties": { + "template": "computer", + "max_calls": 5, + "retries": 1, + "response_length": 1408 + }, + "x": 1000, + "y": 2581, + "width": 210, + "height": 230, + "collapsed": false, + "inherited": false, + "registry": "focal/Focal", + "base_type": "core/Node" } }, "edges": { @@ -1296,9 +1297,6 @@ "d45f07ff-c3ce-4aac-bae6-b9c77089cb69.list": [ "4eb36f21-1020-4609-85e5-d16b42019c66.callbacks" ], - "4eb36f21-1020-4609-85e5-d16b42019c66.state": [ - "3008c0f4-105d-444a-8fde-0ac11a21f40c.state" - ], "3da498c0-55de-4ec3-9943-6486279b9826.value": [ "6d707f1c-af55-481a-970a-eb7a9f9c45dd.message" ], @@ -1380,6 +1378,9 @@ ], "632d4a0e-3327-409b-aaa4-ed38b932286b.list": [ "06175a92-abbe-4483-9ab2-abaee2104728.value" + ], + "4eb36f21-1020-4609-85e5-d16b42019c66.state": [ + "3008c0f4-105d-444a-8fde-0ac11a21f40c.state" ] }, "groups": [ diff --git a/scenes/simulation-suite-v2/templates/computer.jinja2 b/scenes/simulation-suite-v2/templates/computer.jinja2 index c95d5a86..b1ebc8f5 100644 --- a/scenes/simulation-suite-v2/templates/computer.jinja2 +++ b/scenes/simulation-suite-v2/templates/computer.jinja2 @@ -19,7 +19,7 @@ You have access to the following functions you must call to fulfill the user's r focal.callbacks.set_simulated_environment.render( "Create or change the simulated environment. This means the location, time, specific conditions, or any other aspect of the simulation that is not directly related to the characters.", instructions="Instructions on how to change the simulated environment. These will be given to the simulation computer to setup the new environment. REQUIRED.", - reset="If true, the environment should be reset and all simulated characters are removed. If false, the environment should be changed but the characters should remain. REQUIRED.", + reset="If true, the environment should be reset and ALL simulated characters are removed. IMPORTANT: If you set reset=true, this function MUST be the FIRST call in your stack; otherwise, set reset=false to avoid deactivating characters added earlier. REQUIRED.", examples=[ {"instructions": "Change the location to a lush forest, with a river running through it.", "reset":true}, {"instructions": "The simulation suite flickers and changes to a bustling city street.", "reset":true}, @@ -123,7 +123,7 @@ You have access to the following functions you must call to fulfill the user's r {{ focal.callbacks.set_simulation_goal.render( - "Briefly describe the overall goal of the simulation. What is the user looking to experience? What needs to happen for the simulation to be considered complete? This function is used to provide context and direction for the simulation. It should be clear, specific and detailed, and focused on the user's objectives.", + "Briefly describe the overall goal of the simulation. What is the user looking to experience? What needs to happen for the simulation to be considered complete? This function is used to provide context and direction for the simulation. It should be clear, specific and detailed, and focused on the user's objectives. You MUST call this on new simulations or if the user has requested a change in the simulation's goal.", goal="The overall goal of the simulation. This should be a clear and concise statement that outlines the user's objective. REQUIRED.", examples=[ {"goal": "The user is exploring a mysterious alien planet to uncover the secrets of an ancient civilization."}, diff --git a/src/talemate/agents/base.py b/src/talemate/agents/base.py index b8b7d1d2..b83dfa1b 100644 --- a/src/talemate/agents/base.py +++ b/src/talemate/agents/base.py @@ -18,10 +18,11 @@ from talemate.agents.context import ActiveAgent, active_agent from talemate.emit import emit from talemate.events import GameLoopStartEvent from talemate.context import active_scene -import talemate.config as config +from talemate.ux.schema import Column +from talemate.config import get_config, Config +import talemate.config.schema as config_schema from talemate.client.context import ( ClientContext, - set_client_context_attribute, ) __all__ = [ @@ -53,19 +54,20 @@ class AgentActionConfig(pydantic.BaseModel): type: str label: str description: str = "" - value: Union[int, float, str, bool, list, None] = None - default_value: Union[int, float, str, bool] = None - max: Union[int, float, None] = None - min: Union[int, float, None] = None - step: Union[int, float, None] = None + value: int | float | str | bool | list | None = None + default_value: int | float | str | bool | None = None + max: int | float | None = None + min: int | float | None = None + step: int | float | None = None scope: str = "global" - choices: Union[list[dict[str, str]], None] = None + choices: list[dict[str, str | int | float | bool]] | None = None note: Union[str, None] = None expensive: bool = False quick_toggle: bool = False - condition: Union[AgentActionConditional, None] = None - title: Union[str, None] = None - value_migration: Union[Callable, None] = pydantic.Field(default=None, exclude=True) + condition: AgentActionConditional | None = None + title: str | None = None + value_migration: Callable | None = pydantic.Field(default=None, exclude=True) + columns: list[Column] | None = None note_on_value: dict[str, AgentActionNote] = pydantic.Field(default_factory=dict) @@ -78,20 +80,21 @@ class AgentAction(pydantic.BaseModel): label: str description: str = "" warning: str = "" - config: Union[dict[str, AgentActionConfig], None] = None - condition: Union[AgentActionConditional, None] = None + config: dict[str, AgentActionConfig] | None = None + condition: AgentActionConditional | None = None container: bool = False - icon: Union[str, None] = None + icon: str | None = None can_be_disabled: bool = False quick_toggle: bool = False experimental: bool = False class AgentDetail(pydantic.BaseModel): - value: Union[str, None] = None - description: Union[str, None] = None - icon: Union[str, None] = None + value: str | None = None + description: str | None = None + icon: str | None = None color: str = "grey" + hidden: bool = False class DynamicInstruction(pydantic.BaseModel): @@ -172,11 +175,6 @@ def set_processing(fn): if scene: scene.continue_actions() - if getattr(scene, "config", None): - set_client_context_attribute( - "app_config_system_prompts", scene.config.get("system_prompts", {}) - ) - with ActiveAgent(self, fn, args, kwargs) as active_agent_context: try: await self.emit_status(processing=True) @@ -221,7 +219,6 @@ class Agent(ABC): verbose_name = None set_processing = set_processing requires_llm_client = True - auto_break_repetition = False websocket_handler = None essential = True ready_check_error = None @@ -235,6 +232,10 @@ class Agent(ABC): return actions + @property + def config(self) -> Config: + return get_config() + @property def agent_details(self): if hasattr(self, "client"): @@ -244,6 +245,12 @@ class Agent(ABC): @property def ready(self): + if not self.requires_llm_client: + return True + + if not hasattr(self, "client"): + return False + if not getattr(self.client, "enabled", True): return False @@ -326,7 +333,7 @@ class Agent(ABC): # scene state - def context_fingerpint(self, extra: list[str] = []) -> str | None: + def context_fingerprint(self, extra: list[str] = None) -> str | None: active_agent_context = active_agent.get() if not active_agent_context: @@ -337,8 +344,9 @@ class Agent(ABC): else: fingerprint = f"START-{active_agent_context.first.fingerprint}" - for extra_key in extra: - fingerprint += f"-{hash(extra_key)}" + if extra: + for extra_key in extra: + fingerprint += f"-{hash(extra_key)}" return fingerprint @@ -448,25 +456,26 @@ class Agent(ABC): except AttributeError: pass - async def save_config(self, app_config: config.Config | None = None): + async def save_config(self): """ Saves the agent config to the config file. If no config object is provided, the config is loaded from the config file. """ - if not app_config: - app_config: config.Config = config.load_config(as_model=True) + app_config: Config = get_config() - app_config.agents[self.agent_type] = config.Agent( + app_config.agents[self.agent_type] = config_schema.Agent( name=self.agent_type, - client=self.client.name if self.client else None, + client=self.client.name if getattr(self, "client", None) else None, enabled=self.enabled, actions={ - action_key: config.AgentAction( + action_key: config_schema.AgentAction( enabled=action.enabled, config={ - config_key: config.AgentActionConfig(value=config_obj.value) + config_key: config_schema.AgentActionConfig( + value=config_obj.value + ) for config_key, config_obj in action.config.items() }, ) @@ -478,7 +487,8 @@ class Agent(ABC): agent=self.agent_type, config=app_config.agents[self.agent_type], ) - config.save_config(app_config) + + app_config.dirty = True async def on_game_loop_start(self, event: GameLoopStartEvent): """ @@ -602,7 +612,7 @@ class Agent(ABC): exclude_fn: Callable = None, ): current_memory_context = [] - memory_helper = self.scene.get_helper("memory") + memory_helper = instance.get_agent("memory") if memory_helper: history_messages = "\n".join( self.scene.recent_history(memory_history_context_max) diff --git a/src/talemate/agents/conversation/__init__.py b/src/talemate/agents/conversation/__init__.py index 29ae195d..c735248e 100644 --- a/src/talemate/agents/conversation/__init__.py +++ b/src/talemate/agents/conversation/__init__.py @@ -23,6 +23,7 @@ from talemate.agents.base import ( Agent, AgentAction, AgentActionConfig, + AgentActionNote, AgentDetail, AgentEmission, DynamicInstruction, @@ -85,12 +86,22 @@ class ConversationAgent(MemoryRAGMixin, Agent): "format": AgentActionConfig( type="text", label="Format", - description="The generation format of the scene context, as seen by the AI.", + description="The generation format of the scene progression, as seen by the AI. Has no direct effect on your view of the scene, but will affect the way the AI perceives the scene and its characters, leading to changes in the response, for better or worse.", choices=[ {"label": "Screenplay", "value": "movie_script"}, {"label": "Chat (legacy)", "value": "chat"}, + { + "label": "Narrative (NEW, experimental)", + "value": "narrative", + }, ], value="movie_script", + note_on_value={ + "narrative": AgentActionNote( + type="primary", + text="Will attempt to generate flowing, novel-like prose with scene intent awareness and character goal consideration. A reasoning model is STRONGLY recommended. Experimental and more prone to generate out of turn character actions and dialogue.", + ) + }, ), "length": AgentActionConfig( type="number", @@ -133,12 +144,6 @@ class ConversationAgent(MemoryRAGMixin, Agent): ), }, ), - "auto_break_repetition": AgentAction( - enabled=True, - can_be_disabled=True, - label="Auto Break Repetition", - description="Will attempt to automatically break AI repetition.", - ), "content": AgentAction( enabled=True, can_be_disabled=False, @@ -161,7 +166,7 @@ class ConversationAgent(MemoryRAGMixin, Agent): def __init__( self, - client: client.TaleMateClient, + client: client.ClientBase | None = None, kind: Optional[str] = "pygmalion", logging_enabled: Optional[bool] = True, **kwargs, @@ -453,21 +458,31 @@ class ConversationAgent(MemoryRAGMixin, Agent): if total_result.startswith(":\n") or total_result.startswith(": "): total_result = total_result[2:] - # movie script format - # {uppercase character name} - # {dialogue} - total_result = total_result.replace(f"{character.name.upper()}\n", "") + conversation_format = self.conversation_format - # chat format - # {character name}: {dialogue} - total_result = total_result.replace(f"{character.name}:", "") + if conversation_format == "narrative": + # For narrative format, the LLM generates pure prose without character name prefixes + # We need to store it internally in the standard {name}: {text} format + total_result = util.clean_dialogue(total_result, main_name=character.name) + # Only add character name if it's not already there + if not total_result.startswith(character.name + ":"): + total_result = f"{character.name}: {total_result}" + else: + # movie script format + # {uppercase character name} + # {dialogue} + total_result = total_result.replace(f"{character.name.upper()}\n", "") - # Removes partial sentence at the end - total_result = util.clean_dialogue(total_result, main_name=character.name) + # chat format + # {character name}: {dialogue} + total_result = total_result.replace(f"{character.name}:", "") - # Check if total_result starts with character name, if not, prepend it - if not total_result.startswith(character.name + ":"): - total_result = f"{character.name}: {total_result}" + # Removes partial sentence at the end + total_result = util.clean_dialogue(total_result, main_name=character.name) + + # Check if total_result starts with character name, if not, prepend it + if not total_result.startswith(character.name + ":"): + total_result = f"{character.name}: {total_result}" total_result = total_result.strip() @@ -499,9 +514,6 @@ class ConversationAgent(MemoryRAGMixin, Agent): def allow_repetition_break( self, kind: str, agent_function_name: str, auto: bool = False ): - if auto and not self.actions["auto_break_repetition"].enabled: - return False - return agent_function_name == "converse" def inject_prompt_paramters( diff --git a/src/talemate/agents/creator/__init__.py b/src/talemate/agents/creator/__init__.py index de68bd0f..85f30201 100644 --- a/src/talemate/agents/creator/__init__.py +++ b/src/talemate/agents/creator/__init__.py @@ -39,7 +39,7 @@ class CreatorAgent( def __init__( self, - client: client.ClientBase, + client: client.ClientBase | None = None, **kwargs, ): self.client = client diff --git a/src/talemate/agents/creator/modules/create-character.json b/src/talemate/agents/creator/modules/create-character.json index 4fcbc4dc..07a43bca 100644 --- a/src/talemate/agents/creator/modules/create-character.json +++ b/src/talemate/agents/creator/modules/create-character.json @@ -21,7 +21,7 @@ "num": 3 }, "x": 39, - "y": 507, + "y": 236, "width": 210, "height": 154, "collapsed": false, @@ -40,7 +40,7 @@ "num": 1 }, "x": 38, - "y": 107, + "y": -164, "width": 210, "height": 154, "collapsed": false, @@ -59,7 +59,7 @@ "num": 0 }, "x": 38, - "y": -93, + "y": -364, "width": 210, "height": 154, "collapsed": false, @@ -76,7 +76,7 @@ "num": 0 }, "x": 288, - "y": -63, + "y": -334, "width": 210, "height": 106, "collapsed": true, @@ -89,7 +89,7 @@ "id": "553125be-2c2b-4404-98b5-d6333a4f9655", "properties": {}, "x": 348, - "y": 507, + "y": 236, "width": 140, "height": 26, "collapsed": false, @@ -102,7 +102,7 @@ "id": "207e357e-5e83-4d40-a331-d0041b9dfa49", "properties": {}, "x": 348, - "y": 307, + "y": 36, "width": 140, "height": 26, "collapsed": false, @@ -115,7 +115,7 @@ "id": "bad7d2ed-f6fa-452b-8b47-d753ae7a45e0", "properties": {}, "x": 348, - "y": 107, + "y": -164, "width": 140, "height": 26, "collapsed": false, @@ -131,7 +131,7 @@ "scope": "local" }, "x": 598, - "y": 107, + "y": -164, "width": 210, "height": 122, "collapsed": false, @@ -147,7 +147,7 @@ "scope": "local" }, "x": 598, - "y": 307, + "y": 36, "width": 210, "height": 122, "collapsed": false, @@ -163,7 +163,7 @@ "scope": "local" }, "x": 598, - "y": 507, + "y": 236, "width": 210, "height": 122, "collapsed": false, @@ -176,7 +176,7 @@ "id": "1765a51c-82ac-4d96-9c60-d0adc7faaa68", "properties": {}, "x": 498, - "y": 707, + "y": 436, "width": 171, "height": 26, "collapsed": false, @@ -192,7 +192,7 @@ "scope": "local" }, "x": 798, - "y": 706, + "y": 435, "width": 244, "height": 122, "collapsed": false, @@ -205,7 +205,7 @@ "id": "f3dc37df-1748-4235-b575-60e55b8bec73", "properties": {}, "x": 498, - "y": 906, + "y": 635, "width": 171, "height": 26, "collapsed": false, @@ -220,7 +220,7 @@ "stage": 0 }, "x": 1208, - "y": 366, + "y": 95, "width": 210, "height": 118, "collapsed": true, @@ -238,7 +238,7 @@ "icon": "F1719" }, "x": 1178, - "y": -144, + "y": -415, "width": 210, "height": 130, "collapsed": false, @@ -253,7 +253,7 @@ "default": true }, "x": 298, - "y": 736, + "y": 465, "width": 210, "height": 58, "collapsed": true, @@ -268,7 +268,7 @@ "default": false }, "x": 298, - "y": 936, + "y": 665, "width": 210, "height": 58, "collapsed": true, @@ -287,7 +287,7 @@ "num": 2 }, "x": 38, - "y": 306, + "y": 35, "width": 210, "height": 154, "collapsed": false, @@ -303,7 +303,7 @@ "scope": "local" }, "x": 798, - "y": 906, + "y": 635, "width": 244, "height": 122, "collapsed": false, @@ -322,7 +322,7 @@ "num": 5 }, "x": 38, - "y": 706, + "y": 435, "width": 237, "height": 154, "collapsed": false, @@ -341,7 +341,7 @@ "num": 7 }, "x": 38, - "y": 906, + "y": 635, "width": 210, "height": 154, "collapsed": false, @@ -360,7 +360,7 @@ "num": 4 }, "x": 48, - "y": 1326, + "y": 1055, "width": 237, "height": 154, "collapsed": false, @@ -375,7 +375,7 @@ "default": true }, "x": 318, - "y": 1356, + "y": 1085, "width": 210, "height": 58, "collapsed": true, @@ -388,7 +388,7 @@ "id": "6d387c67-6b32-4435-b984-4760f0f1f8d2", "properties": {}, "x": 498, - "y": 1336, + "y": 1065, "width": 171, "height": 26, "collapsed": false, @@ -404,7 +404,7 @@ "scope": "local" }, "x": 798, - "y": 1316, + "y": 1045, "width": 244, "height": 122, "collapsed": false, @@ -455,7 +455,7 @@ "num": 6 }, "x": 38, - "y": 1106, + "y": 835, "width": 252, "height": 154, "collapsed": false, @@ -471,7 +471,7 @@ "apply_on_unresolved": true }, "x": 518, - "y": 1136, + "y": 865, "width": 210, "height": 102, "collapsed": true, @@ -1101,7 +1101,7 @@ "num": 8 }, "x": 49, - "y": 1546, + "y": 1275, "width": 210, "height": 154, "collapsed": false, @@ -1116,7 +1116,7 @@ "default": false }, "x": 329, - "y": 1586, + "y": 1315, "width": 210, "height": 58, "collapsed": true, @@ -1129,7 +1129,7 @@ "id": "8acfe789-fbb5-4e29-8fd8-2217b987c086", "properties": {}, "x": 499, - "y": 1566, + "y": 1295, "width": 171, "height": 26, "collapsed": false, @@ -1145,7 +1145,7 @@ "scope": "local" }, "x": 799, - "y": 1526, + "y": 1255, "width": 244, "height": 122, "collapsed": false, @@ -1258,8 +1258,8 @@ "output_name": "character", "num": 0 }, - "x": 331, - "y": 5739, + "x": 332, + "y": 6178, "width": 210, "height": 106, "collapsed": false, @@ -1274,8 +1274,8 @@ "name": "character", "scope": "local" }, - "x": 51, - "y": 5729, + "x": 52, + "y": 6168, "width": 210, "height": 122, "collapsed": false, @@ -1291,8 +1291,8 @@ "output_name": "actor", "num": 0 }, - "x": 331, - "y": 5949, + "x": 332, + "y": 6388, "width": 210, "height": 106, "collapsed": false, @@ -1307,8 +1307,8 @@ "name": "actor", "scope": "local" }, - "x": 51, - "y": 5949, + "x": 52, + "y": 6388, "width": 210, "height": 122, "collapsed": false, @@ -1323,7 +1323,7 @@ "stage": 0 }, "x": 1320, - "y": 1160, + "y": 889, "width": 210, "height": 118, "collapsed": true, @@ -1414,7 +1414,7 @@ "writing_style": null }, "x": 320, - "y": 1200, + "y": 929, "width": 270, "height": 122, "collapsed": true, @@ -1430,7 +1430,7 @@ "scope": "local" }, "x": 790, - "y": 1110, + "y": 839, "width": 244, "height": 122, "collapsed": false, @@ -1475,6 +1475,159 @@ "inherited": false, "registry": "agents/creator/ContextualGenerate", "base_type": "core/Node" + }, + "d61de1ad-6f2a-447f-918a-dce7e76ea3a1": { + "title": "assign_voice", + "id": "d61de1ad-6f2a-447f-918a-dce7e76ea3a1", + "properties": {}, + "x": 509, + "y": 1544, + "width": 171, + "height": 26, + "collapsed": false, + "inherited": false, + "registry": "core/Watch", + "base_type": "core/Node" + }, + "3d655827-b66b-4355-910d-96097e7f2f13": { + "title": "SET local.assign_voice", + "id": "3d655827-b66b-4355-910d-96097e7f2f13", + "properties": { + "name": "assign_voice", + "scope": "local" + }, + "x": 810, + "y": 1506, + "width": 244, + "height": 122, + "collapsed": false, + "inherited": false, + "registry": "state/SetState", + "base_type": "core/Node" + }, + "6aa5c32a-8dfb-48a9-96ec-5ad9ed6aa5d1": { + "title": "Stage 0", + "id": "6aa5c32a-8dfb-48a9-96ec-5ad9ed6aa5d1", + "properties": { + "stage": 0 + }, + "x": 1170, + "y": 1546, + "width": 210, + "height": 118, + "collapsed": true, + "inherited": false, + "registry": "core/Stage", + "base_type": "core/Node" + }, + "9ac9b12d-1b97-4f42-92d8-0d4f883ffb2f": { + "title": "IN assign_voice", + "id": "9ac9b12d-1b97-4f42-92d8-0d4f883ffb2f", + "properties": { + "input_type": "bool", + "input_name": "assign_voice", + "input_optional": true, + "input_group": "", + "num": 9 + }, + "x": 60, + "y": 1527, + "width": 210, + "height": 154, + "collapsed": false, + "inherited": false, + "registry": "core/Input", + "base_type": "core/Node" + }, + "de11206d-13db-44a5-befd-de559fb68d09": { + "title": "GET local.assign_voice", + "id": "de11206d-13db-44a5-befd-de559fb68d09", + "properties": { + "name": "assign_voice", + "scope": "local" + }, + "x": 25, + "y": 5720, + "width": 240, + "height": 122, + "collapsed": false, + "inherited": false, + "registry": "state/GetState", + "base_type": "core/Node" + }, + "97b196a3-e7a6-4cfa-905e-686a744890b7": { + "title": "Switch", + "id": "97b196a3-e7a6-4cfa-905e-686a744890b7", + "properties": { + "pass_through": true + }, + "x": 355, + "y": 5740, + "width": 210, + "height": 78, + "collapsed": false, + "inherited": false, + "registry": "core/Switch", + "base_type": "core/Node" + }, + "3956711a-a3df-4213-b739-104cbe704964": { + "title": "GET local.character", + "id": "3956711a-a3df-4213-b739-104cbe704964", + "properties": { + "name": "character", + "scope": "local" + }, + "x": 25, + "y": 5930, + "width": 210, + "height": 122, + "collapsed": false, + "inherited": false, + "registry": "state/GetState", + "base_type": "core/Node" + }, + "47b2b492-4178-4254-b468-3877a5341f66": { + "title": "Assign Voice", + "id": "47b2b492-4178-4254-b468-3877a5341f66", + "properties": {}, + "x": 665, + "y": 5830, + "width": 161, + "height": 66, + "collapsed": false, + "inherited": false, + "registry": "agents/director/AssignVoice", + "base_type": "core/Node" + }, + "44d78795-2d41-468b-94d5-399b9b655888": { + "title": "Stage 6", + "id": "44d78795-2d41-468b-94d5-399b9b655888", + "properties": { + "stage": 6 + }, + "x": 885, + "y": 5860, + "width": 210, + "height": 118, + "collapsed": true, + "inherited": false, + "registry": "core/Stage", + "base_type": "core/Node" + }, + "f5e5ec03-cc12-4a45-aa15-6ca3e5e4bc85": { + "title": "As Bool", + "id": "f5e5ec03-cc12-4a45-aa15-6ca3e5e4bc85", + "properties": { + "default": true + }, + "x": 330, + "y": 1560, + "width": 210, + "height": 58, + "collapsed": true, + "inherited": false, + "registry": "core/AsBool", + "base_type": "core/Node" } }, "edges": { @@ -1726,15 +1879,39 @@ ], "4acb67ea-68ee-43ae-a8c6-98a2b0e0f053.text": [ "d41f0d98-14d5-49dd-8e57-7812fb9fee94.value" + ], + "d61de1ad-6f2a-447f-918a-dce7e76ea3a1.value": [ + "3d655827-b66b-4355-910d-96097e7f2f13.value" + ], + "3d655827-b66b-4355-910d-96097e7f2f13.value": [ + "6aa5c32a-8dfb-48a9-96ec-5ad9ed6aa5d1.state" + ], + "9ac9b12d-1b97-4f42-92d8-0d4f883ffb2f.value": [ + "f5e5ec03-cc12-4a45-aa15-6ca3e5e4bc85.value" + ], + "de11206d-13db-44a5-befd-de559fb68d09.value": [ + "97b196a3-e7a6-4cfa-905e-686a744890b7.value" + ], + "97b196a3-e7a6-4cfa-905e-686a744890b7.yes": [ + "47b2b492-4178-4254-b468-3877a5341f66.state" + ], + "3956711a-a3df-4213-b739-104cbe704964.value": [ + "47b2b492-4178-4254-b468-3877a5341f66.character" + ], + "47b2b492-4178-4254-b468-3877a5341f66.state": [ + "44d78795-2d41-468b-94d5-399b9b655888.state" + ], + "f5e5ec03-cc12-4a45-aa15-6ca3e5e4bc85.value": [ + "d61de1ad-6f2a-447f-918a-dce7e76ea3a1.value" ] }, "groups": [ { "title": "Process Arguments - Stage 0", "x": 1, - "y": -218, - "width": 1446, - "height": 1948, + "y": -490, + "width": 1432, + "height": 2216, "color": "#3f789e", "font_size": 24, "inherited": false @@ -1792,12 +1969,22 @@ { "title": "Outputs", "x": 0, - "y": 5642, + "y": 6080, "width": 595, "height": 472, "color": "#8A8", "font_size": 24, "inherited": false + }, + { + "title": "Assign Voice - Stage 6", + "x": 0, + "y": 5640, + "width": 1120, + "height": 437, + "color": "#3f789e", + "font_size": 24, + "inherited": false } ], "comments": [], @@ -1805,6 +1992,7 @@ "base_type": "core/Graph", "inputs": [], "outputs": [], + "module_properties": {}, "style": { "title_color": "#572e44", "node_color": "#392c34", diff --git a/src/talemate/agents/director/__init__.py b/src/talemate/agents/director/__init__.py index 0da53d74..a8b1b780 100644 --- a/src/talemate/agents/director/__init__.py +++ b/src/talemate/agents/director/__init__.py @@ -1,33 +1,24 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List - import structlog -import traceback -import talemate.instance as instance from talemate.emit import emit -from talemate.scene_message import DirectorMessage -from talemate.util import random_color -from talemate.character import deactivate_character -from talemate.status import LoadingStatus -from talemate.exceptions import GenerationCancelled +from talemate.scene_message import DirectorMessage, Flags -from talemate.agents.base import Agent, AgentAction, AgentActionConfig, set_processing +from talemate.agents.base import Agent, AgentAction, AgentActionConfig from talemate.agents.registry import register from talemate.agents.memory.rag import MemoryRAGMixin +from talemate.client import ClientBase +from talemate.game.focal.schema import Call from .guide import GuideSceneMixin from .generate_choices import GenerateChoicesMixin from .legacy_scene_instructions import LegacySceneInstructionsMixin from .auto_direct import AutoDirectMixin from .websocket_handler import DirectorWebsocketHandler - +from .character_management import CharacterManagementMixin import talemate.agents.director.nodes # noqa: F401 -if TYPE_CHECKING: - from talemate import Character, Scene - log = structlog.get_logger("talemate.agent.director") @@ -38,6 +29,7 @@ class DirectorAgent( GenerateChoicesMixin, AutoDirectMixin, LegacySceneInstructionsMixin, + CharacterManagementMixin, Agent, ): agent_type = "director" @@ -76,9 +68,10 @@ class DirectorAgent( GenerateChoicesMixin.add_actions(actions) GuideSceneMixin.add_actions(actions) AutoDirectMixin.add_actions(actions) + CharacterManagementMixin.add_actions(actions) return actions - def __init__(self, client, **kwargs): + def __init__(self, client: ClientBase | None = None, **kwargs): self.is_enabled = True self.client = client self.next_direct_character = {} @@ -101,178 +94,24 @@ class DirectorAgent( def actor_direction_mode(self): return self.actions["direct"].config["actor_direction_mode"].value - @set_processing - async def persist_characters_from_worldstate( - self, exclude: list[str] = None - ) -> List[Character]: - created_characters = [] - - for character_name in self.scene.world_state.characters.keys(): - if exclude and character_name.lower() in exclude: - continue - - if character_name in self.scene.character_names: - continue - - character = await self.persist_character(name=character_name) - - created_characters.append(character) - - self.scene.emit_status() - - return created_characters - - @set_processing - async def persist_character( - self, - name: str, - content: str = None, - attributes: str = None, - determine_name: bool = True, - templates: list[str] = None, - active: bool = True, - narrate_entry: bool = False, - narrate_entry_direction: str = "", - augment_attributes: str = "", - generate_attributes: bool = True, - description: str = "", - ) -> Character: - world_state = instance.get_agent("world_state") - creator = instance.get_agent("creator") - narrator = instance.get_agent("narrator") - memory = instance.get_agent("memory") - scene: "Scene" = self.scene - any_attribute_templates = False - - loading_status = LoadingStatus(max_steps=None, cancellable=True) - - # Start of character creation - log.debug("persist_character", name=name) - - # Determine the character's name (or clarify if it's already set) - if determine_name: - loading_status("Determining character name") - name = await creator.determine_character_name(name, instructions=content) - log.debug("persist_character", adjusted_name=name) - - # Create the blank character - character: Character = self.scene.Character(name=name) - - # Add the character to the scene - character.color = random_color() - actor = self.scene.Actor( - character=character, agent=instance.get_agent("conversation") + async def log_function_call(self, call: Call): + log.debug("director.log_function_call", call=call) + message = DirectorMessage( + message=f"Called {call.name}", + action=call.name, + flags=Flags.HIDDEN, + subtype="function_call", ) - await self.scene.add_actor(actor) + emit("director", message, data={"function_call": call.model_dump()}) - try: - # Apply any character generation templates - if templates: - loading_status("Applying character generation templates") - templates = scene.world_state_manager.template_collection.collect_all( - templates - ) - log.debug("persist_character", applying_templates=templates) - await scene.world_state_manager.apply_templates( - templates.values(), - character_name=character.name, - information=content, - ) - - # if any of the templates are attribute templates, then we no longer need to - # generate a character sheet - any_attribute_templates = any( - template.template_type == "character_attribute" - for template in templates.values() - ) - log.debug( - "persist_character", any_attribute_templates=any_attribute_templates - ) - - if ( - any_attribute_templates - and augment_attributes - and generate_attributes - ): - log.debug( - "persist_character", augmenting_attributes=augment_attributes - ) - loading_status("Augmenting character attributes") - additional_attributes = await world_state.extract_character_sheet( - name=name, - text=content, - augmentation_instructions=augment_attributes, - ) - character.base_attributes.update(additional_attributes) - - # Generate a character sheet if there are no attribute templates - if not any_attribute_templates and generate_attributes: - loading_status("Generating character sheet") - log.debug("persist_character", extracting_character_sheet=True) - if not attributes: - attributes = await world_state.extract_character_sheet( - name=name, text=content - ) - else: - attributes = world_state._parse_character_sheet(attributes) - - log.debug("persist_character", attributes=attributes) - character.base_attributes = attributes - - # Generate a description for the character - if not description: - loading_status("Generating character description") - description = await creator.determine_character_description( - character, information=content - ) - character.description = description - log.debug("persist_character", description=description) - - # Generate a dialogue instructions for the character - loading_status("Generating acting instructions") - dialogue_instructions = ( - await creator.determine_character_dialogue_instructions( - character, information=content - ) - ) - character.dialogue_instructions = dialogue_instructions - log.debug("persist_character", dialogue_instructions=dialogue_instructions) - - # Narrate the character's entry if the option is selected - if active and narrate_entry: - loading_status("Narrating character entry") - is_present = await world_state.is_character_present(name) - if not is_present: - await narrator.action_to_narration( - "narrate_character_entry", - emit_message=True, - character=character, - narrative_direction=narrate_entry_direction, - ) - - # Deactivate the character if not active - if not active: - await deactivate_character(scene, character) - - # Commit the character's details to long term memory - await character.commit_to_memory(memory) - self.scene.emit_status() - self.scene.world_state.emit() - - loading_status.done( - message=f"{character.name} added to scene", status="success" - ) - return character - except GenerationCancelled: - loading_status.done(message="Character creation cancelled", status="idle") - await scene.remove_actor(actor) - except Exception: - loading_status.done(message="Character creation failed", status="error") - await scene.remove_actor(actor) - log.error("Error persisting character", error=traceback.format_exc()) - - async def log_action(self, action: str, action_description: str): - message = DirectorMessage(message=action_description, action=action) + async def log_action( + self, action: str, action_description: str, console_only: bool = False + ): + message = DirectorMessage( + message=action_description, + action=action, + flags=Flags.HIDDEN if console_only else Flags.NONE, + ) self.scene.push_history(message) emit("director", message) diff --git a/src/talemate/agents/director/character_management.py b/src/talemate/agents/director/character_management.py new file mode 100644 index 00000000..5e6b404a --- /dev/null +++ b/src/talemate/agents/director/character_management.py @@ -0,0 +1,333 @@ +from typing import TYPE_CHECKING +import traceback +import structlog +import talemate.instance as instance +import talemate.agents.tts.voice_library as voice_library +from talemate.agents.tts.schema import Voice +from talemate.util import random_color +from talemate.character import deactivate_character, set_voice +from talemate.status import LoadingStatus +from talemate.exceptions import GenerationCancelled +from talemate.agents.base import AgentAction, AgentActionConfig, set_processing +import talemate.game.focal as focal + + +__all__ = [ + "CharacterManagementMixin", +] + +log = structlog.get_logger() + +if TYPE_CHECKING: + from talemate import Character, Scene + from talemate.agents.tts import TTSAgent + + +class VoiceCandidate(Voice): + used: bool = False + + +class CharacterManagementMixin: + """ + Director agent mixin that provides functionality for automatically guiding + the actors or the narrator during the scene progression. + """ + + @classmethod + def add_actions(cls, actions: dict[str, AgentAction]): + actions["character_management"] = AgentAction( + enabled=True, + container=True, + can_be_disabled=False, + label="Character Management", + icon="mdi-account", + description="Configure how the director manages characters.", + config={ + "assign_voice": AgentActionConfig( + type="bool", + label="Assign Voice (TTS)", + description="If enabled, the director is allowed to assign a text-to-speech voice when persisting a character.", + value=True, + title="Persisting Characters", + ), + }, + ) + + # config property helpers + + @property + def cm_assign_voice(self) -> bool: + return self.actions["character_management"].config["assign_voice"].value + + @property + def cm_should_assign_voice(self) -> bool: + if not self.cm_assign_voice: + return False + + tts_agent: "TTSAgent" = instance.get_agent("tts") + if not tts_agent.enabled: + return False + + if not tts_agent.ready_apis: + return False + + return True + + # actions + + @set_processing + async def persist_characters_from_worldstate( + self, exclude: list[str] = None + ) -> list["Character"]: + created_characters = [] + + for character_name in self.scene.world_state.characters.keys(): + if exclude and character_name.lower() in exclude: + continue + + if character_name in self.scene.character_names: + continue + + character = await self.persist_character(name=character_name) + + created_characters.append(character) + + self.scene.emit_status() + + return created_characters + + @set_processing + async def persist_character( + self, + name: str, + content: str = None, + attributes: str = None, + determine_name: bool = True, + templates: list[str] = None, + active: bool = True, + narrate_entry: bool = False, + narrate_entry_direction: str = "", + augment_attributes: str = "", + generate_attributes: bool = True, + description: str = "", + assign_voice: bool = True, + is_player: bool = False, + ) -> "Character": + world_state = instance.get_agent("world_state") + creator = instance.get_agent("creator") + narrator = instance.get_agent("narrator") + memory = instance.get_agent("memory") + scene: "Scene" = self.scene + any_attribute_templates = False + + loading_status = LoadingStatus(max_steps=None, cancellable=True) + + # Start of character creation + log.debug("persist_character", name=name) + + # Determine the character's name (or clarify if it's already set) + if determine_name: + loading_status("Determining character name") + name = await creator.determine_character_name(name, instructions=content) + log.debug("persist_character", adjusted_name=name) + + # Create the blank character + character: "Character" = self.scene.Character(name=name, is_player=is_player) + + # Add the character to the scene + character.color = random_color() + + if is_player: + actor = self.scene.Player( + character=character, agent=instance.get_agent("conversation") + ) + else: + actor = self.scene.Actor( + character=character, agent=instance.get_agent("conversation") + ) + + await self.scene.add_actor(actor) + + try: + # Apply any character generation templates + if templates: + loading_status("Applying character generation templates") + templates = scene.world_state_manager.template_collection.collect_all( + templates + ) + log.debug("persist_character", applying_templates=templates) + await scene.world_state_manager.apply_templates( + templates.values(), + character_name=character.name, + information=content, + ) + + # if any of the templates are attribute templates, then we no longer need to + # generate a character sheet + any_attribute_templates = any( + template.template_type == "character_attribute" + for template in templates.values() + ) + log.debug( + "persist_character", any_attribute_templates=any_attribute_templates + ) + + if ( + any_attribute_templates + and augment_attributes + and generate_attributes + ): + log.debug( + "persist_character", augmenting_attributes=augment_attributes + ) + loading_status("Augmenting character attributes") + additional_attributes = await world_state.extract_character_sheet( + name=name, + text=content, + augmentation_instructions=augment_attributes, + ) + character.base_attributes.update(additional_attributes) + + # Generate a character sheet if there are no attribute templates + if not any_attribute_templates and generate_attributes: + loading_status("Generating character sheet") + log.debug("persist_character", extracting_character_sheet=True) + if not attributes: + attributes = await world_state.extract_character_sheet( + name=name, text=content + ) + else: + attributes = world_state._parse_character_sheet(attributes) + + log.debug("persist_character", attributes=attributes) + character.base_attributes = attributes + + # Generate a description for the character + if not description: + loading_status("Generating character description") + description = await creator.determine_character_description( + character, information=content + ) + character.description = description + log.debug("persist_character", description=description) + + # Generate a dialogue instructions for the character + loading_status("Generating acting instructions") + dialogue_instructions = ( + await creator.determine_character_dialogue_instructions( + character, information=content + ) + ) + character.dialogue_instructions = dialogue_instructions + log.debug("persist_character", dialogue_instructions=dialogue_instructions) + + # Narrate the character's entry if the option is selected + if active and narrate_entry: + loading_status("Narrating character entry") + is_present = await world_state.is_character_present(name) + if not is_present: + await narrator.action_to_narration( + "narrate_character_entry", + emit_message=True, + character=character, + narrative_direction=narrate_entry_direction, + ) + + if assign_voice: + await self.assign_voice_to_character(character) + + # Deactivate the character if not active + if not active: + await deactivate_character(scene, character) + + # Commit the character's details to long term memory + await character.commit_to_memory(memory) + self.scene.emit_status() + self.scene.world_state.emit() + + loading_status.done( + message=f"{character.name} added to scene", status="success" + ) + return character + except GenerationCancelled: + loading_status.done(message="Character creation cancelled", status="idle") + await scene.remove_actor(actor) + except Exception: + loading_status.done(message="Character creation failed", status="error") + await scene.remove_actor(actor) + log.error("Error persisting character", error=traceback.format_exc()) + + @set_processing + async def assign_voice_to_character( + self, character: "Character" + ) -> list[focal.Call]: + tts_agent: "TTSAgent" = instance.get_agent("tts") + if not self.cm_should_assign_voice: + log.debug("assign_voice_to_character", skip=True, reason="not enabled") + return + + vl: voice_library.VoiceLibrary = voice_library.get_instance() + + ready_tts_apis = tts_agent.ready_apis + + voices_global = voice_library.voices_for_apis(ready_tts_apis, vl) + voices_scene = voice_library.voices_for_apis( + ready_tts_apis, self.scene.voice_library + ) + + voices = voices_global + voices_scene + + if not voices: + log.debug( + "assign_voice_to_character", skip=True, reason="no voices available" + ) + return + + voice_candidates = { + voice.id: VoiceCandidate(**voice.model_dump()) for voice in voices + } + + for scene_character in self.scene.all_characters: + if scene_character.voice: + voice_candidates[scene_character.voice.id].used = True + + async def assign_voice(voice_id: str): + voice = vl.get_voice(voice_id) or self.scene.voice_library.get_voice( + voice_id + ) + if not voice: + log.error( + "assign_voice_to_character", + skip=True, + reason="voice not found", + voice_id=voice_id, + ) + return + await set_voice(character, voice, auto=True) + await self.log_action( + f"Assigned voice `{voice.label}` to `{character.name}`", + "Assigned voice", + console_only=True, + ) + + focal_handler = focal.Focal( + self.client, + callbacks=[ + focal.Callback( + name="assign_voice", + arguments=[focal.Argument(name="voice_id", type="str")], + fn=assign_voice, + ), + ], + max_calls=1, + character=character, + voices=list(voice_candidates.values()), + scene=self.scene, + narrator_voice=tts_agent.narrator_voice, + ) + + await focal_handler.request("director.cm-assign-voice") + + log.debug("assign_voice_to_character", calls=focal_handler.state.calls) + + return focal_handler.state.calls diff --git a/src/talemate/agents/director/guide.py b/src/talemate/agents/director/guide.py index 34df323f..786f90ff 100644 --- a/src/talemate/agents/director/guide.py +++ b/src/talemate/agents/director/guide.py @@ -231,7 +231,9 @@ class GuideSceneMixin: if cached_guidance: if not analysis: return cached_guidance.get("guidance") - elif cached_guidance.get("fp") == self.context_fingerpint(extra=[analysis]): + elif cached_guidance.get("fp") == self.context_fingerprint( + extra=[analysis] + ): return cached_guidance.get("guidance") return None @@ -250,7 +252,7 @@ class GuideSceneMixin: self.set_scene_states( **{ key: { - "fp": self.context_fingerpint(extra=[analysis]), + "fp": self.context_fingerprint(extra=[analysis]), "guidance": guidance, "analysis_type": analysis_type, "character": character.name if character else None, diff --git a/src/talemate/agents/director/nodes.py b/src/talemate/agents/director/nodes.py index 569826e6..3e3fe784 100644 --- a/src/talemate/agents/director/nodes.py +++ b/src/talemate/agents/director/nodes.py @@ -7,7 +7,7 @@ from talemate.game.engine.nodes.core import ( ) from talemate.game.engine.nodes.registry import register from talemate.game.engine.nodes.agent import AgentSettingsNode, AgentNode - +from talemate.character import Character TYPE_CHOICES.extend( [ @@ -77,3 +77,90 @@ class PersistCharacter(AgentNode): ) self.set_output_values({"state": state, "character": character}) + + +@register("agents/director/AssignVoice") +class AssignVoice(AgentNode): + """ + Assigns a voice to a character. + """ + + _agent_name: ClassVar[str] = "director" + + def __init__(self, title="Assign Voice", **kwargs): + super().__init__(title=title, **kwargs) + + def setup(self): + self.add_input("state") + self.add_input("character", socket_type="character") + + self.add_output("state") + self.add_output("character", socket_type="character") + self.add_output("voice", socket_type="tts/voice") + + async def run(self, state: GraphState): + character: "Character" = self.require_input("character") + + await self.agent.assign_voice_to_character(character) + + voice = character.voice + + self.set_output_values({"state": state, "character": character, "voice": voice}) + + +@register("agents/director/LogAction") +class LogAction(AgentNode): + """ + Logs an action to the console. + """ + + _agent_name: ClassVar[str] = "director" + + class Fields: + action = PropertyField( + name="action", + type="str", + description="The action to log", + default="", + ) + action_description = PropertyField( + name="action_description", + type="str", + description="The description of the action", + default="", + ) + console_only = PropertyField( + name="console_only", + type="bool", + description="Whether to log the action to the console only", + default=False, + ) + + def __init__(self, title="Log Director Action", **kwargs): + super().__init__(title=title, **kwargs) + + def setup(self): + self.add_input("state") + self.add_input("action", socket_type="str") + self.add_input("action_description", socket_type="str") + self.add_input("console_only", socket_type="bool", optional=True) + + self.set_property("action", "") + self.set_property("action_description", "") + self.set_property("console_only", False) + + self.add_output("state") + + async def run(self, state: GraphState): + state = self.require_input("state") + action = self.require_input("action") + action_description = self.require_input("action_description") + console_only = self.normalized_input_value("console_only") or False + + await self.agent.log_action( + action=action, + action_description=action_description, + console_only=console_only, + ) + + self.set_output_values({"state": state}) diff --git a/src/talemate/agents/director/websocket_handler.py b/src/talemate/agents/director/websocket_handler.py index b8617a62..0c7451a0 100644 --- a/src/talemate/agents/director/websocket_handler.py +++ b/src/talemate/agents/director/websocket_handler.py @@ -5,8 +5,9 @@ from typing import TYPE_CHECKING from talemate.instance import get_agent from talemate.server.websocket_plugin import Plugin -from talemate.context import interaction +from talemate.context import interaction, handle_generation_cancelled from talemate.status import set_loading +from talemate.exceptions import GenerationCancelled if TYPE_CHECKING: from talemate.tale_mate import Scene @@ -45,6 +46,12 @@ class PersistCharacterPayload(pydantic.BaseModel): content: str = "" description: str = "" + is_player: bool = False + + +class AssignVoiceToCharacterPayload(pydantic.BaseModel): + character_name: str + class DirectorWebsocketHandler(Plugin): """ @@ -105,7 +112,13 @@ class DirectorWebsocketHandler(Plugin): async def handle_task_done(task): if task.exception(): - log.error("Error persisting character", error=task.exception()) + exc = task.exception() + log.error("Error persisting character", error=exc) + + # Handle GenerationCancelled properly to reset cancel_requested flag + if isinstance(exc, GenerationCancelled): + handle_generation_cancelled(exc) + await self.signal_operation_failed("Error persisting character") else: self.websocket_handler.queue_put( @@ -118,3 +131,63 @@ class DirectorWebsocketHandler(Plugin): await self.signal_operation_done() task.add_done_callback(lambda task: asyncio.create_task(handle_task_done(task))) + + async def handle_assign_voice_to_character(self, data: dict): + """ + Assign a voice to a character using the director agent + """ + try: + payload = AssignVoiceToCharacterPayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + scene: "Scene" = self.scene + if not scene: + await self.signal_operation_failed("No scene active") + return + + character = scene.get_character(payload.character_name) + if not character: + await self.signal_operation_failed( + f"Character '{payload.character_name}' not found" + ) + return + + character.voice = None + + # Add as asyncio task + task = asyncio.create_task(self.director.assign_voice_to_character(character)) + + async def handle_task_done(task): + if task.exception(): + exc = task.exception() + log.error("Error assigning voice to character", error=exc) + + # Handle GenerationCancelled properly to reset cancel_requested flag + if isinstance(exc, GenerationCancelled): + handle_generation_cancelled(exc) + + self.websocket_handler.queue_put( + { + "type": self.router, + "action": "assign_voice_to_character_failed", + "character_name": payload.character_name, + "error": str(exc), + } + ) + await self.signal_operation_failed( + f"Error assigning voice to character: {exc}" + ) + else: + self.websocket_handler.queue_put( + { + "type": self.router, + "action": "assign_voice_to_character_done", + "character_name": payload.character_name, + } + ) + await self.signal_operation_done() + self.scene.emit_status() + + task.add_done_callback(lambda task: asyncio.create_task(handle_task_done(task))) diff --git a/src/talemate/agents/editor/__init__.py b/src/talemate/agents/editor/__init__.py index f86c9f27..9719d97c 100644 --- a/src/talemate/agents/editor/__init__.py +++ b/src/talemate/agents/editor/__init__.py @@ -6,6 +6,7 @@ import structlog import talemate.emit.async_signals import talemate.util as util +from talemate.client import ClientBase from talemate.prompts import Prompt from talemate.agents.base import Agent, AgentAction, AgentActionConfig, set_processing @@ -86,7 +87,7 @@ class EditorAgent( RevisionMixin.add_actions(actions) return actions - def __init__(self, client, **kwargs): + def __init__(self, client: ClientBase | None = None, **kwargs): self.client = client self.is_enabled = True self.actions = EditorAgent.init_actions() diff --git a/src/talemate/agents/editor/websocket_handler.py b/src/talemate/agents/editor/websocket_handler.py index 194d4e5a..75107c26 100644 --- a/src/talemate/agents/editor/websocket_handler.py +++ b/src/talemate/agents/editor/websocket_handler.py @@ -60,5 +60,8 @@ class EditorWebsocketHandler(Plugin): character=character, ) revised = await editor.revision_revise(info) + if isinstance(message, CharacterMessage): + if not revised.startswith(character.name + ":"): + revised = f"{character.name}: {revised}" scene.edit_message(message.id, revised) diff --git a/src/talemate/agents/memory/__init__.py b/src/talemate/agents/memory/__init__.py index 446d7a92..897def6f 100644 --- a/src/talemate/agents/memory/__init__.py +++ b/src/talemate/agents/memory/__init__.py @@ -23,10 +23,9 @@ from talemate.agents.base import ( AgentDetail, set_processing, ) -from talemate.config import load_config +from talemate.config.schema import EmbeddingFunctionPreset from talemate.context import scene_is_loading, active_scene from talemate.emit import emit -from talemate.emit.signals import handlers import talemate.emit.async_signals as async_signals from talemate.agents.memory.context import memory_request, MemoryRequest from talemate.agents.memory.exceptions import ( @@ -107,14 +106,13 @@ class MemoryAgent(Agent): } return actions - def __init__(self, scene, **kwargs): + def __init__(self, **kwargs): self.db = None - self.scene = scene self.memory_tracker = {} - self.config = load_config() self._ready_to_add = False - handlers["config_saved"].connect(self.on_config_saved) + async_signals.get("config.changed").connect(self.on_config_changed) + async_signals.get("client.embeddings_available").connect( self.on_client_embeddings_available ) @@ -136,28 +134,29 @@ class MemoryAgent(Agent): @property def get_presets(self): - def _label(embedding: dict): - prefix = ( - embedding["client"] if embedding["client"] else embedding["embeddings"] - ) - if embedding["model"]: - return f"{prefix}: {embedding['model']}" + def _label(embedding: EmbeddingFunctionPreset): + prefix = embedding.client if embedding.client else embedding.embeddings + if embedding.model: + return f"{prefix}: {embedding.model}" else: return f"{prefix}" return [ {"value": k, "label": _label(v)} - for k, v in self.config.get("presets", {}).get("embeddings", {}).items() + for k, v in self.config.presets.embeddings.items() ] @property def embeddings_config(self): _embeddings = self.actions["_config"].config["embeddings"].value - return self.config.get("presets", {}).get("embeddings", {}).get(_embeddings, {}) + return self.config.presets.embeddings.get(_embeddings) @property def embeddings(self): - return self.embeddings_config.get("embeddings", "sentence-transformer") + try: + return self.embeddings_config.embeddings + except AttributeError: + return None @property def using_openai_embeddings(self): @@ -181,22 +180,31 @@ class MemoryAgent(Agent): @property def embeddings_client(self): - return self.embeddings_config.get("client") + try: + return self.embeddings_config.client + except AttributeError: + return None @property def max_distance(self) -> float: - distance = float(self.embeddings_config.get("distance", 1.0)) - distance_mod = float(self.embeddings_config.get("distance_mod", 1.0)) + distance = float(self.embeddings_config.distance) + distance_mod = float(self.embeddings_config.distance_mod) return distance * distance_mod @property def model(self): - return self.embeddings_config.get("model") + try: + return self.embeddings_config.model + except AttributeError: + return None @property def distance_function(self): - return self.embeddings_config.get("distance_function", "l2") + try: + return self.embeddings_config.distance_function + except AttributeError: + return None @property def device(self) -> str: @@ -204,7 +212,10 @@ class MemoryAgent(Agent): @property def trust_remote_code(self) -> bool: - return self.embeddings_config.get("trust_remote_code", False) + try: + return self.embeddings_config.trust_remote_code + except AttributeError: + return False @property def fingerprint(self) -> str: @@ -241,7 +252,7 @@ class MemoryAgent(Agent): if self.using_sentence_transformer_embeddings and not self.model: self.actions["_config"].config["embeddings"].value = "default" - if not scene or not scene.get_helper("memory"): + if not scene or not scene.active: return self.close_db(scene) @@ -255,7 +266,14 @@ class MemoryAgent(Agent): self.actions["_config"].config["embeddings"].choices = self.get_presets return self.actions["_config"].config["embeddings"].choices - def on_config_saved(self, event): + async def fix_broken_embeddings(self): + if not self.embeddings_config: + self.actions["_config"].config["embeddings"].value = "default" + await self.emit_status() + await self.handle_embeddings_change() + await self.save_config() + + async def on_config_changed(self, event): loop = asyncio.get_running_loop() openai_key = self.openai_api_key @@ -263,7 +281,6 @@ class MemoryAgent(Agent): old_presets = self.actions["_config"].config["embeddings"].choices.copy() - self.config = load_config() new_presets = self.sync_presets() if fingerprint != self.fingerprint: log.warning( @@ -285,10 +302,13 @@ class MemoryAgent(Agent): if emit_status: loop.run_until_complete(self.emit_status()) + await self.fix_broken_embeddings() + async def on_client_embeddings_available(self, event: "ClientEmbeddingsStatus"): current_embeddings = self.actions["_config"].config["embeddings"].value if current_embeddings == event.client.embeddings_identifier: + event.seen = True return if not self.using_client_api_embeddings or not self.ready: @@ -304,6 +324,7 @@ class MemoryAgent(Agent): await self.emit_status() await self.handle_embeddings_change() await self.save_config() + event.seen = True @set_processing async def set_db(self): @@ -837,7 +858,7 @@ class ChromaDBMemoryAgent(MemoryAgent): @property def openai_api_key(self): - return self.config.get("openai", {}).get("api_key") + return self.config.openai.api_key @property def embedding_function(self) -> Callable: diff --git a/src/talemate/agents/narrator/__init__.py b/src/talemate/agents/narrator/__init__.py index f8bcbc52..6d0e8910 100644 --- a/src/talemate/agents/narrator/__init__.py +++ b/src/talemate/agents/narrator/__init__.py @@ -138,11 +138,6 @@ class NarratorAgent(MemoryRAGMixin, Agent): ), }, ), - "auto_break_repetition": AgentAction( - enabled=True, - label="Auto Break Repetition", - description="Will attempt to automatically break AI repetition.", - ), "content": AgentAction( enabled=True, can_be_disabled=False, @@ -210,7 +205,7 @@ class NarratorAgent(MemoryRAGMixin, Agent): def __init__( self, - client: client.TaleMateClient, + client: client.ClientBase | None = None, **kwargs, ): self.client = client @@ -753,9 +748,6 @@ class NarratorAgent(MemoryRAGMixin, Agent): def allow_repetition_break( self, kind: str, agent_function_name: str, auto: bool = False ): - if auto and not self.actions["auto_break_repetition"].enabled: - return False - return True def set_generation_overrides(self, prompt_param: dict): diff --git a/src/talemate/agents/summarize/__init__.py b/src/talemate/agents/summarize/__init__.py index f111e475..1ee6a010 100644 --- a/src/talemate/agents/summarize/__init__.py +++ b/src/talemate/agents/summarize/__init__.py @@ -16,7 +16,7 @@ from talemate.scene_message import ( ReinforcementMessage, ) from talemate.world_state.templates import GenerationOptions - +from talemate.client import ClientBase from talemate.agents.base import ( Agent, AgentAction, @@ -34,6 +34,7 @@ from talemate.history import ArchiveEntry from .analyze_scene import SceneAnalyzationMixin from .context_investigation import ContextInvestigationMixin from .layered_history import LayeredHistoryMixin +from .tts_utils import TTSUtilsMixin if TYPE_CHECKING: from talemate.tale_mate import Character @@ -71,6 +72,7 @@ class SummarizeAgent( ContextInvestigationMixin, # Needs to be after ContextInvestigationMixin so signals are connected in the right order SceneAnalyzationMixin, + TTSUtilsMixin, Agent, ): """ @@ -129,7 +131,7 @@ class SummarizeAgent( ContextInvestigationMixin.add_actions(actions) return actions - def __init__(self, client, **kwargs): + def __init__(self, client: ClientBase | None = None, **kwargs): self.client = client self.actions = SummarizeAgent.init_actions() diff --git a/src/talemate/agents/summarize/analyze_scene.py b/src/talemate/agents/summarize/analyze_scene.py index f9012d8d..29431a30 100644 --- a/src/talemate/agents/summarize/analyze_scene.py +++ b/src/talemate/agents/summarize/analyze_scene.py @@ -16,12 +16,29 @@ from talemate.agents.conversation import ConversationAgentEmission from talemate.agents.narrator import NarratorAgentEmission from talemate.agents.context import active_agent from talemate.agents.base import RagBuildSubInstructionEmission +from contextvars import ContextVar if TYPE_CHECKING: from talemate.tale_mate import Character log = structlog.get_logger() +## CONTEXT + +scene_analysis_disabled_context = ContextVar("scene_analysis_disabled", default=False) + + +class SceneAnalysisDisabled: + """ + Context manager to disable scene analysis during specific agent actions. + """ + + def __enter__(self): + self.token = scene_analysis_disabled_context.set(True) + + def __exit__(self, _exc_type, _exc_value, _traceback): + scene_analysis_disabled_context.reset(self.token) + talemate.emit.async_signals.register( "agent.summarization.scene_analysis.before", @@ -184,6 +201,16 @@ class SceneAnalyzationMixin: if not self.analyze_scene: return + try: + if scene_analysis_disabled_context.get(): + log.debug( + "on_inject_instructions: scene analysis disabled through context", + emission=emission, + ) + return + except LookupError: + pass + analyze_scene_for_type = getattr(self, f"analyze_scene_for_{emission_type}") if not analyze_scene_for_type: @@ -259,7 +286,14 @@ class SceneAnalyzationMixin: if not cached_analysis: return None - fingerprint = self.context_fingerpint() + fingerprint = self.context_fingerprint() + + log.debug( + "get_cached_analysis", + fingerprint=fingerprint, + cached_analysis_fp=cached_analysis.get("fp"), + match=cached_analysis.get("fp") == fingerprint, + ) if cached_analysis.get("fp") == fingerprint: return cached_analysis["guidance"] @@ -271,7 +305,7 @@ class SceneAnalyzationMixin: Sets the cached analysis for the given type. """ - fingerprint = self.context_fingerpint() + fingerprint = self.context_fingerprint() self.set_scene_states( **{ diff --git a/src/talemate/agents/summarize/tts_utils.py b/src/talemate/agents/summarize/tts_utils.py new file mode 100644 index 00000000..7c2caeb2 --- /dev/null +++ b/src/talemate/agents/summarize/tts_utils.py @@ -0,0 +1,59 @@ +import structlog +from talemate.agents.base import ( + set_processing, +) +from talemate.prompts import Prompt +from talemate.status import set_loading +from talemate.util.dialogue import separate_dialogue_from_exposition + +log = structlog.get_logger("talemate.agents.summarize.tts_utils") + + +class TTSUtilsMixin: + """ + Summarizer Mixin for text-to-speech utilities. + """ + + @set_loading("Preparing TTS context") + @set_processing + async def markup_context_for_tts(self, text: str) -> str: + """ + Markup the context for text-to-speech. + """ + + original_text = text + + log.debug("Markup context for TTS", text=text) + + # if there are no quotes in the text, there is nothing to separate + if '"' not in text: + return original_text + + # here we separate dialogue from exposition because into + # obvious segments. It seems to have a positive effect on some + # LLMs returning the complete text. + separate_chunks = separate_dialogue_from_exposition(text) + + numbered_chunks = [] + for i, chunk in enumerate(separate_chunks): + numbered_chunks.append(f"[{i + 1}] {chunk.text.strip()}") + + text = "\n".join(numbered_chunks) + + response = await Prompt.request( + "summarizer.markup-context-for-tts", + self.client, + "investigate_1024", + vars={ + "text": text, + "max_tokens": self.client.max_token_length, + "scene": self.scene, + }, + ) + + try: + response = response.split("")[1].split("")[0].strip() + return response + except IndexError: + log.error("Failed to extract markup from response", response=response) + return original_text diff --git a/src/talemate/agents/tts.py b/src/talemate/agents/tts.py deleted file mode 100644 index dc4575aa..00000000 --- a/src/talemate/agents/tts.py +++ /dev/null @@ -1,670 +0,0 @@ -from __future__ import annotations - -import asyncio -import base64 -import functools -import io -import os -import tempfile -import time -import uuid -from typing import Union - -import httpx -import nltk -import pydantic -import structlog -from nltk.tokenize import sent_tokenize -from openai import AsyncOpenAI - -import talemate.config as config -import talemate.emit.async_signals -import talemate.instance as instance -from talemate.emit import emit -from talemate.emit.signals import handlers -from talemate.events import GameLoopNewMessageEvent -from talemate.scene_message import CharacterMessage, NarratorMessage - -from .base import ( - Agent, - AgentAction, - AgentActionConditional, - AgentActionConfig, - AgentDetail, - set_processing, -) -from .registry import register - -try: - from TTS.api import TTS -except ImportError: - TTS = None - -log = structlog.get_logger("talemate.agents.tts") # - -if not TTS: - # TTS installation is massive and requires a lot of dependencies - # so we don't want to require it unless the user wants to use it - log.info( - "TTS (local) requires the TTS package, please install with `pip install TTS` if you want to use the local api" - ) - - -def parse_chunks(text: str) -> list[str]: - """ - Takes a string and splits it into chunks based on punctuation. - - In case of an error it will return the original text as a single chunk and - the error will be logged. - """ - - try: - text = text.replace("...", "__ellipsis__") - chunks = sent_tokenize(text) - cleaned_chunks = [] - - for chunk in chunks: - chunk = chunk.replace("*", "") - if not chunk: - continue - cleaned_chunks.append(chunk) - - for i, chunk in enumerate(cleaned_chunks): - chunk = chunk.replace("__ellipsis__", "...") - cleaned_chunks[i] = chunk - - return cleaned_chunks - except Exception as e: - log.error("chunking error", error=e, text=text) - return [text.replace("__ellipsis__", "...").replace("*", "")] - - -def clean_quotes(chunk: str): - # if there is an uneven number of quotes, remove the last one if its - # at the end of the chunk. If its in the middle, add a quote to the end - if chunk.count('"') % 2 == 1: - if chunk.endswith('"'): - chunk = chunk[:-1] - else: - chunk += '"' - - return chunk - - -def rejoin_chunks(chunks: list[str], chunk_size: int = 250): - """ - Will combine chunks split by punctuation into a single chunk until - max chunk size is reached - """ - - joined_chunks = [] - - current_chunk = "" - - for chunk in chunks: - if len(current_chunk) + len(chunk) > chunk_size: - joined_chunks.append(clean_quotes(current_chunk)) - current_chunk = "" - - current_chunk += chunk - - if current_chunk: - joined_chunks.append(clean_quotes(current_chunk)) - return joined_chunks - - -class Voice(pydantic.BaseModel): - value: str - label: str - - -class VoiceLibrary(pydantic.BaseModel): - api: str - voices: list[Voice] = pydantic.Field(default_factory=list) - last_synced: float = None - - -@register() -class TTSAgent(Agent): - """ - Text to speech agent - """ - - agent_type = "tts" - verbose_name = "Voice" - requires_llm_client = False - essential = False - - @classmethod - def config_options(cls, agent=None): - config_options = super().config_options(agent=agent) - - if agent: - config_options["actions"]["_config"]["config"]["voice_id"]["choices"] = [ - voice.model_dump() for voice in agent.list_voices_sync() - ] - - return config_options - - def __init__(self, **kwargs): - self.is_enabled = False # - - try: - nltk.data.find("tokenizers/punkt") - except LookupError: - try: - nltk.download("punkt", quiet=True) - except Exception as e: - log.error("nltk download error", error=e) - except Exception as e: - log.error("nltk find error", error=e) - - self.voices = { - "elevenlabs": VoiceLibrary(api="elevenlabs"), - "tts": VoiceLibrary(api="tts"), - "openai": VoiceLibrary(api="openai"), - } - self.config = config.load_config() - self.playback_done_event = asyncio.Event() - self.preselect_voice = None - self.actions = { - "_config": AgentAction( - enabled=True, - label="Configure", - description="TTS agent configuration", - config={ - "api": AgentActionConfig( - type="text", - choices=[ - {"value": "tts", "label": "TTS (Local)"}, - {"value": "elevenlabs", "label": "Eleven Labs"}, - {"value": "openai", "label": "OpenAI"}, - ], - value="tts", - label="API", - description="Which TTS API to use", - onchange="emit", - ), - "voice_id": AgentActionConfig( - type="text", - value="default", - label="Narrator Voice", - description="Voice ID/Name to use for TTS", - choices=[], - ), - "generate_for_player": AgentActionConfig( - type="bool", - value=False, - label="Generate for player", - description="Generate audio for player messages", - ), - "generate_for_npc": AgentActionConfig( - type="bool", - value=True, - label="Generate for NPCs", - description="Generate audio for NPC messages", - ), - "generate_for_narration": AgentActionConfig( - type="bool", - value=True, - label="Generate for narration", - description="Generate audio for narration messages", - ), - "generate_chunks": AgentActionConfig( - type="bool", - value=False, - label="Split generation", - description="Generate audio chunks for each sentence - will be much more responsive but may loose context to inform inflection", - ), - }, - ), - "openai": AgentAction( - enabled=True, - container=True, - icon="mdi-server-outline", - condition=AgentActionConditional( - attribute="_config.config.api", value="openai" - ), - label="OpenAI", - config={ - "model": AgentActionConfig( - type="text", - value="tts-1", - choices=[ - {"value": "tts-1", "label": "TTS 1"}, - {"value": "tts-1-hd", "label": "TTS 1 HD"}, - ], - label="Model", - description="TTS model to use", - ), - }, - ), - } - - self.actions["_config"].model_dump() - handlers["config_saved"].connect(self.on_config_saved) - - @property - def enabled(self): - return self.is_enabled - - @property - def has_toggle(self): - return True - - @property - def experimental(self): - return False - - @property - def not_ready_reason(self) -> str: - """ - Returns a string explaining why the agent is not ready - """ - - if self.ready: - return "" - - if self.api == "tts": - if not TTS: - return "TTS not installed" - - elif self.requires_token and not self.token: - return "No API token" - - elif not self.default_voice_id: - return "No voice selected" - - @property - def agent_details(self): - details = { - "api": AgentDetail( - icon="mdi-server-outline", - value=self.api_label, - description="The backend to use for TTS", - ).model_dump(), - } - - if self.ready and self.enabled: - details["voice"] = AgentDetail( - icon="mdi-account-voice", - value=self.voice_id_to_label(self.default_voice_id) or "", - description="The voice to use for TTS", - color="info", - ).model_dump() - elif self.enabled: - details["error"] = AgentDetail( - icon="mdi-alert", - value=self.not_ready_reason, - description=self.not_ready_reason, - color="error", - ).model_dump() - - return details - - @property - def api(self): - return self.actions["_config"].config["api"].value - - @property - def api_label(self): - choices = self.actions["_config"].config["api"].choices - api = self.api - for choice in choices: - if choice["value"] == api: - return choice["label"] - return api - - @property - def token(self): - api = self.api - return self.config.get(api, {}).get("api_key") - - @property - def default_voice_id(self): - return self.actions["_config"].config["voice_id"].value - - @property - def requires_token(self): - return self.api != "tts" - - @property - def ready(self): - if self.api == "tts": - if not TTS: - return False - return True - - return (not self.requires_token or self.token) and self.default_voice_id - - @property - def status(self): - if not self.enabled: - return "disabled" - if self.ready: - if getattr(self, "processing_bg", 0) > 0: - return "busy_bg" if not getattr(self, "processing", False) else "busy" - return "active" if not getattr(self, "processing", False) else "busy" - if self.requires_token and not self.token: - return "error" - if self.api == "tts": - if not TTS: - return "error" - return "uninitialized" - - @property - def max_generation_length(self): - if self.api == "elevenlabs": - return 1024 - elif self.api == "coqui": - return 250 - - return 250 - - @property - def openai_api_key(self): - return self.config.get("openai", {}).get("api_key") - - async def apply_config(self, *args, **kwargs): - try: - api = kwargs["actions"]["_config"]["config"]["api"]["value"] - except KeyError: - api = self.api - - api_changed = api != self.api - - # log.debug( - # "apply_config", - # api=api, - # api_changed=api != self.api, - # current_api=self.api, - # args=args, - # kwargs=kwargs, - # ) - - try: - self.preselect_voice = kwargs["actions"]["_config"]["config"]["voice_id"][ - "value" - ] - except KeyError: - self.preselect_voice = self.default_voice_id - - await super().apply_config(*args, **kwargs) - - if api_changed: - try: - self.actions["_config"].config["voice_id"].value = ( - self.voices[api].voices[0].value - ) - except IndexError: - self.actions["_config"].config["voice_id"].value = "" - - def connect(self, scene): - super().connect(scene) - talemate.emit.async_signals.get("game_loop_new_message").connect( - self.on_game_loop_new_message - ) - - def on_config_saved(self, event): - config = event.data - self.config = config - instance.emit_agent_status(self.__class__, self) - - async def on_game_loop_new_message(self, emission: GameLoopNewMessageEvent): - """ - Called when a conversation is generated - """ - - if not self.enabled or not self.ready: - return - - if not isinstance(emission.message, (CharacterMessage, NarratorMessage)): - return - - if ( - isinstance(emission.message, NarratorMessage) - and not self.actions["_config"].config["generate_for_narration"].value - ): - return - - if isinstance(emission.message, CharacterMessage): - if ( - emission.message.source == "player" - and not self.actions["_config"].config["generate_for_player"].value - ): - return - elif ( - emission.message.source == "ai" - and not self.actions["_config"].config["generate_for_npc"].value - ): - return - - if isinstance(emission.message, CharacterMessage): - character_prefix = emission.message.split(":", 1)[0] - else: - character_prefix = "" - - log.info( - "reactive tts", message=emission.message, character_prefix=character_prefix - ) - - await self.generate(str(emission.message).replace(character_prefix + ": ", "")) - - def voice(self, voice_id: str) -> Union[Voice, None]: - for voice in self.voices[self.api].voices: - if voice.value == voice_id: - return voice - return None - - def voice_id_to_label(self, voice_id: str): - for voice in self.voices[self.api].voices: - if voice.value == voice_id: - return voice.label - return None - - def list_voices_sync(self): - loop = asyncio.get_event_loop() - return loop.run_until_complete(self.list_voices()) - - async def list_voices(self): - if self.requires_token and not self.token: - return [] - - library = self.voices[self.api] - - # TODO: allow re-syncing voices - if library.last_synced: - return library.voices - - list_fn = getattr(self, f"_list_voices_{self.api}") - log.info("Listing voices", api=self.api) - - library.voices = await list_fn() - library.last_synced = time.time() - - if self.preselect_voice: - if self.voice(self.preselect_voice): - self.actions["_config"].config["voice_id"].value = self.preselect_voice - self.preselect_voice = None - - # if the current voice cannot be found, reset it - if not self.voice(self.default_voice_id): - self.actions["_config"].config["voice_id"].value = "" - - # set loading to false - return library.voices - - @set_processing - async def generate(self, text: str): - if not self.enabled or not self.ready or not text: - return - - self.playback_done_event.set() - - generate_fn = getattr(self, f"_generate_{self.api}") - - if self.actions["_config"].config["generate_chunks"].value: - chunks = parse_chunks(text) - chunks = rejoin_chunks(chunks) - else: - chunks = parse_chunks(text) - chunks = rejoin_chunks(chunks, chunk_size=self.max_generation_length) - - # Start generating audio chunks in the background - generation_task = asyncio.create_task(self.generate_chunks(generate_fn, chunks)) - await self.set_background_processing(generation_task) - - # Wait for both tasks to complete - # await asyncio.gather(generation_task) - - async def generate_chunks(self, generate_fn, chunks): - for chunk in chunks: - chunk = chunk.replace("*", "").strip() - log.info("Generating audio", api=self.api, chunk=chunk) - audio_data = await generate_fn(chunk) - self.play_audio(audio_data) - - def play_audio(self, audio_data): - # play audio through the python audio player - # play(audio_data) - - emit( - "audio_queue", - data={"audio_data": base64.b64encode(audio_data).decode("utf-8")}, - ) - - self.playback_done_event.set() # Signal that playback is finished - - # LOCAL - - async def _generate_tts(self, text: str) -> Union[bytes, None]: - if not TTS: - return - - tts_config = self.config.get("tts", {}) - model = tts_config.get("model") - device = tts_config.get("device", "cpu") - - log.debug("tts local", model=model, device=device) - - if not hasattr(self, "tts_instance"): - self.tts_instance = TTS(model).to(device) - - tts = self.tts_instance - - loop = asyncio.get_event_loop() - - voice = self.voice(self.default_voice_id) - - with tempfile.TemporaryDirectory() as temp_dir: - file_path = os.path.join(temp_dir, f"tts-{uuid.uuid4()}.wav") - - await loop.run_in_executor( - None, - functools.partial( - tts.tts_to_file, - text=text, - speaker_wav=voice.value, - language="en", - file_path=file_path, - ), - ) - # tts.tts_to_file(text=text, speaker_wav=voice.value, language="en", file_path=file_path) - - with open(file_path, "rb") as f: - return f.read() - - async def _list_voices_tts(self) -> dict[str, str]: - return [ - Voice(**voice) for voice in self.config.get("tts", {}).get("voices", []) - ] - - # ELEVENLABS - - async def _generate_elevenlabs( - self, text: str, chunk_size: int = 1024 - ) -> Union[bytes, None]: - api_key = self.token - if not api_key: - return - - async with httpx.AsyncClient() as client: - url = f"https://api.elevenlabs.io/v1/text-to-speech/{self.default_voice_id}" - headers = { - "Accept": "audio/mpeg", - "Content-Type": "application/json", - "xi-api-key": api_key, - } - data = { - "text": text, - "model_id": self.config.get("elevenlabs", {}).get("model"), - "voice_settings": {"stability": 0.5, "similarity_boost": 0.5}, - } - - response = await client.post(url, json=data, headers=headers, timeout=300) - - if response.status_code == 200: - bytes_io = io.BytesIO() - for chunk in response.iter_bytes(chunk_size=chunk_size): - if chunk: - bytes_io.write(chunk) - - # Put the audio data in the queue for playback - return bytes_io.getvalue() - else: - log.error(f"Error generating audio: {response.text}") - - async def _list_voices_elevenlabs(self) -> dict[str, str]: - url_voices = "https://api.elevenlabs.io/v1/voices" - - voices = [] - - async with httpx.AsyncClient() as client: - headers = { - "Accept": "application/json", - "xi-api-key": self.token, - } - response = await client.get( - url_voices, headers=headers, params={"per_page": 1000} - ) - speakers = response.json()["voices"] - voices.extend( - [ - Voice(value=speaker["voice_id"], label=speaker["name"]) - for speaker in speakers - ] - ) - - # sort by name - voices.sort(key=lambda x: x.label) - - return voices - - # OPENAI - - async def _generate_openai(self, text: str, chunk_size: int = 1024): - client = AsyncOpenAI(api_key=self.openai_api_key) - - model = self.actions["openai"].config["model"].value - - response = await client.audio.speech.create( - model=model, voice=self.default_voice_id, input=text - ) - - bytes_io = io.BytesIO() - for chunk in response.iter_bytes(chunk_size=chunk_size): - if chunk: - bytes_io.write(chunk) - - # Put the audio data in the queue for playback - return bytes_io.getvalue() - - async def _list_voices_openai(self) -> dict[str, str]: - return [ - Voice(value="alloy", label="Alloy"), - Voice(value="echo", label="Echo"), - Voice(value="fable", label="Fable"), - Voice(value="onyx", label="Onyx"), - Voice(value="nova", label="Nova"), - Voice(value="shimmer", label="Shimmer"), - ] diff --git a/src/talemate/agents/tts/__init__.py b/src/talemate/agents/tts/__init__.py new file mode 100644 index 00000000..1158510f --- /dev/null +++ b/src/talemate/agents/tts/__init__.py @@ -0,0 +1,995 @@ +from __future__ import annotations + +import asyncio +import base64 +import re +import traceback +from typing import TYPE_CHECKING + +import uuid +from collections import deque + +import structlog +from nltk.tokenize import sent_tokenize + +import talemate.util.dialogue as dialogue_utils +import talemate.emit.async_signals as async_signals +import talemate.instance as instance +from talemate.ux.schema import Note +from talemate.emit import emit +from talemate.events import GameLoopNewMessageEvent +from talemate.scene_message import ( + CharacterMessage, + NarratorMessage, + ContextInvestigationMessage, +) +from talemate.agents.base import ( + Agent, + AgentAction, + AgentActionConfig, + AgentDetail, + AgentActionNote, + set_processing, +) +from talemate.agents.registry import register + +from .schema import ( + APIStatus, + Voice, + VoiceLibrary, + GenerationContext, + Chunk, + VoiceGenerationEmission, +) +from .providers import provider + +import talemate.agents.tts.voice_library as voice_library + +from .elevenlabs import ElevenLabsMixin +from .openai import OpenAIMixin +from .google import GoogleMixin +from .kokoro import KokoroMixin +from .chatterbox import ChatterboxMixin +from .websocket_handler import TTSWebsocketHandler +from .f5tts import F5TTSMixin + +import talemate.agents.tts.nodes as tts_nodes # noqa: F401 + +if TYPE_CHECKING: + from talemate.character import Character, VoiceChangedEvent + from talemate.agents.summarize import SummarizeAgent + from talemate.game.engine.nodes.scene import SceneLoopEvent + +log = structlog.get_logger("talemate.agents.tts") + +HOT_SWAP_NOTIFICATION_TIME = 60 + +VOICE_LIBRARY_NOTE = "Voices are not managed here, but in the voice library which can be accessed through the Talemate application bar at the top. When disabling/enabling APIS, close and open this window to refresh the choices." + +async_signals.register( + "agent.tts.prepare.before", + "agent.tts.prepare.after", + "agent.tts.generate.before", + "agent.tts.generate.after", +) + + +def parse_chunks(text: str) -> list[str]: + """ + Takes a string and splits it into chunks based on punctuation. + + In case of an error it will return the original text as a single chunk and + the error will be logged. + """ + + try: + text = text.replace("*", "") + + # ensure sentence terminators are before quotes + # otherwise the beginning of dialog will bleed into narration + text = re.sub(r'([^.?!]+) "', r'\1. "', text) + + text = text.replace("...", "__ellipsis__") + chunks = sent_tokenize(text) + cleaned_chunks = [] + + for chunk in chunks: + if not chunk.strip(): + continue + cleaned_chunks.append(chunk) + + for i, chunk in enumerate(cleaned_chunks): + chunk = chunk.replace("__ellipsis__", "...") + cleaned_chunks[i] = chunk + + return cleaned_chunks + except Exception as e: + log.error("chunking error", error=e, text=text) + return [text.replace("__ellipsis__", "...").replace("*", "")] + + +def rejoin_chunks(chunks: list[str], chunk_size: int = 250): + """ + Will combine chunks split by punctuation into a single chunk until + max chunk size is reached + """ + + joined_chunks = [] + + current_chunk = "" + + for chunk in chunks: + if len(current_chunk) + len(chunk) > chunk_size: + joined_chunks.append(current_chunk) + current_chunk = "" + + current_chunk += chunk + + if current_chunk: + joined_chunks.append(current_chunk) + return joined_chunks + + +@register() +class TTSAgent( + ElevenLabsMixin, + OpenAIMixin, + GoogleMixin, + KokoroMixin, + ChatterboxMixin, + F5TTSMixin, + Agent, +): + """ + Text to speech agent + """ + + agent_type = "tts" + verbose_name = "Voice" + requires_llm_client = False + essential = False + + # websocket handler for frontend voice library management + websocket_handler = TTSWebsocketHandler + + @classmethod + def config_options(cls, agent=None): + config_options = super().config_options(agent=agent) + + if not agent: + return config_options + + narrator_voice_id = config_options["actions"]["_config"]["config"][ + "narrator_voice_id" + ] + + narrator_voice_id["choices"] = cls.narrator_voice_id_choices(agent) + + return config_options + + @classmethod + def narrator_voice_id_choices(cls, agent: "TTSAgent") -> list[dict[str, str]]: + choices = voice_library.voices_for_apis(agent.ready_apis, agent.voice_library) + choices.sort(key=lambda x: x.label) + return [ + { + "label": f"{voice.label} ({voice.provider})", + "value": voice.id, + } + for voice in choices + ] + + @classmethod + def init_actions(cls) -> dict[str, AgentAction]: + actions = { + "_config": AgentAction( + enabled=True, + label="Configure", + description="TTS agent configuration", + config={ + "apis": AgentActionConfig( + type="flags", + value=[ + "kokoro", + ], + label="Enabled APIs", + description="APIs to use for TTS", + choices=[], + ), + "narrator_voice_id": AgentActionConfig( + type="autocomplete", + value="kokoro:am_adam", + label="Narrator Voice", + description="Voice to use for narration", + choices=[], + note=VOICE_LIBRARY_NOTE, + ), + "speaker_separation": AgentActionConfig( + type="text", + value="simple", + label="Speaker separation", + description="How to separate speaker dialogue from exposition", + choices=[ + {"label": "No separation", "value": "none"}, + {"label": "Simple", "value": "simple"}, + {"label": "Mixed", "value": "mixed"}, + {"label": "AI assisted", "value": "ai_assisted"}, + ], + note_on_value={ + "none": AgentActionNote( + type="primary", + text="Character messages will be voiced entirely by the character's voice with a fallback to the narrator voice if the character has no voice selecte. Narrator messages will be voiced exclusively by the narrator voice.", + ), + "simple": AgentActionNote( + type="primary", + text="Exposition and dialogue will be separated in character messages. Narrator messages will be voiced exclusively by the narrator voice. This means", + ), + "mixed": AgentActionNote( + type="primary", + text="A mix of `simple` and `ai_assisted`. Character messages are separated into narrator and the character's voice. Narrator messages that have dialogue are analyzed by the Summarizer agent to determine the appropriate speaker(s).", + ), + "ai_assisted": AgentActionNote( + type="primary", + text="Appropriate speaker separation will be attempted based on the content of the message with help from the Summarizer agent. This sends an extra prompt to the LLM to determine the appropriate speaker(s).", + ), + }, + ), + "generate_for_player": AgentActionConfig( + type="bool", + value=False, + label="Auto-generate for player", + description="Generate audio for player messages", + ), + "generate_for_npc": AgentActionConfig( + type="bool", + value=True, + label="Auto-generate for AI characters", + description="Generate audio for NPC messages", + ), + "generate_for_narration": AgentActionConfig( + type="bool", + value=True, + label="Auto-generate for narration", + description="Generate audio for narration messages", + ), + "generate_for_context_investigation": AgentActionConfig( + type="bool", + value=True, + label="Auto-generate for context investigation", + description="Generate audio for context investigation messages", + ), + }, + ), + } + + KokoroMixin.add_actions(actions) + ChatterboxMixin.add_actions(actions) + GoogleMixin.add_actions(actions) + ElevenLabsMixin.add_actions(actions) + OpenAIMixin.add_actions(actions) + F5TTSMixin.add_actions(actions) + + return actions + + def __init__(self, **kwargs): + self.is_enabled = False # tts agent is disabled by default + self.actions = TTSAgent.init_actions() + self.playback_done_event = asyncio.Event() + + # Queue management for voice generation + # Each queue instance gets a unique id so it can later be referenced + # (e.g. for cancellation of all remaining items). + # Only one queue can be active at a time. New generation requests that + # arrive while a queue is processing will be appended to the same + # queue. Once the queue is fully processed it is discarded and a new + # one will be created for subsequent generation requests. + # Queue now holds individual (context, chunk) pairs so interruption can + # happen between chunks even when a single context produced many. + self._generation_queue: deque[tuple[GenerationContext, Chunk]] = deque() + self._queue_id: str | None = None + self._queue_task: asyncio.Task | None = None + self._queue_lock = asyncio.Lock() + + # general helpers + + @property + def enabled(self): + return self.is_enabled + + @property + def has_toggle(self): + return True + + @property + def experimental(self): + return False + + @property + def voice_library(self) -> VoiceLibrary: + return voice_library.get_instance() + + # config helpers + + @property + def narrator_voice_id(self) -> str: + return self.actions["_config"].config["narrator_voice_id"].value + + @property + def generate_for_player(self) -> bool: + return self.actions["_config"].config["generate_for_player"].value + + @property + def generate_for_npc(self) -> bool: + return self.actions["_config"].config["generate_for_npc"].value + + @property + def generate_for_narration(self) -> bool: + return self.actions["_config"].config["generate_for_narration"].value + + @property + def generate_for_context_investigation(self) -> bool: + return ( + self.actions["_config"].config["generate_for_context_investigation"].value + ) + + @property + def speaker_separation(self) -> str: + return self.actions["_config"].config["speaker_separation"].value + + @property + def apis(self) -> list[str]: + return self.actions["_config"].config["apis"].value + + @property + def all_apis(self) -> list[str]: + return [api["value"] for api in self.actions["_config"].config["apis"].choices] + + @property + def agent_details(self): + details = {} + + self.actions["_config"].config[ + "narrator_voice_id" + ].choices = self.narrator_voice_id_choices(self) + + if not self.enabled: + return details + + used_apis: set[str] = set() + + used_disabled_apis: set[str] = set() + + if self.narrator_voice: + # + + label = self.narrator_voice.label + color = "primary" + used_apis.add(self.narrator_voice.provider) + + if not self.api_enabled(self.narrator_voice.provider): + used_disabled_apis.add(self.narrator_voice.provider) + + if not self.api_ready(self.narrator_voice.provider): + color = "error" + + details["narrator_voice"] = AgentDetail( + icon="mdi-script-text", + value=label, + description="Default voice", + color=color, + ).model_dump() + + scene = getattr(self, "scene", None) + if scene: + for character in scene.characters: + if character.voice: + label = character.voice.label + color = "primary" + used_apis.add(character.voice.provider) + if not self.api_enabled(character.voice.provider): + used_disabled_apis.add(character.voice.provider) + if not self.api_ready(character.voice.provider): + color = "error" + + details[f"{character.name}_voice"] = AgentDetail( + icon="mdi-account-voice", + value=f"{character.name}", + description=f"{character.name}'s voice: {label} ({character.voice.provider})", + color=color, + ).model_dump() + + for api in used_disabled_apis: + details[f"{api}_disabled"] = AgentDetail( + icon="mdi-alert-circle", + value=f"{api} disabled", + description=f"{api} disabled - at least one voice is attempting to use this api but is not enabled", + color="error", + ).model_dump() + + for api in used_apis: + fn = getattr(self, f"{api}_agent_details", None) + if fn: + details.update(fn) + return details + + @property + def status(self): + if not self.enabled: + return "disabled" + if self.ready: + if getattr(self, "processing_bg", 0) > 0: + return "busy_bg" if not getattr(self, "processing", False) else "busy" + return "idle" if not getattr(self, "processing", False) else "busy" + return "uninitialized" + + @property + def narrator_voice(self) -> Voice | None: + return self.voice_library.get_voice(self.narrator_voice_id) + + @property + def api_status(self) -> list[APIStatus]: + api_status: list[APIStatus] = [] + + for api in self.all_apis: + not_configured_reason = getattr(self, f"{api}_not_configured_reason", None) + not_configured_action = getattr(self, f"{api}_not_configured_action", None) + api_info: str | None = getattr(self, f"{api}_info", None) + messages: list[Note] = [] + if not_configured_reason: + messages.append( + Note( + text=not_configured_reason, + color="error", + icon="mdi-alert-circle-outline", + actions=[not_configured_action] + if not_configured_action + else None, + ) + ) + if api_info: + messages.append( + Note( + text=api_info.strip(), + color="muted", + icon="mdi-information-outline", + ) + ) + _status = APIStatus( + api=api, + enabled=self.api_enabled(api), + ready=self.api_ready(api), + configured=self.api_configured(api), + messages=messages, + supports_mixing=getattr(self, f"{api}_supports_mixing", False), + provider=provider(api), + default_model=getattr(self, f"{api}_model", None), + model_choices=getattr(self, f"{api}_model_choices", []), + ) + api_status.append(_status) + + # order by api + api_status.sort(key=lambda x: x.api) + + return api_status + + # events + + def connect(self, scene): + super().connect(scene) + async_signals.get("game_loop_new_message").connect( + self.on_game_loop_new_message + ) + async_signals.get("voice_library.update.after").connect( + self.on_voice_library_update + ) + async_signals.get("scene_loop_init_after").connect(self.on_scene_loop_init) + async_signals.get("character.voice_changed").connect( + self.on_character_voice_changed + ) + + async def on_scene_loop_init(self, event: "SceneLoopEvent"): + if not self.enabled or not self.ready or not self.generate_for_narration: + return + + if self.scene.environment == "creative": + return + + content_messages = self.scene.last_message_of_type( + ["character", "narrator", "context_investigation"] + ) + + if content_messages: + # we already have a history, so we don't need to generate TTS for the intro + return + + await self.generate(self.scene.get_intro(), character=None) + + async def on_voice_library_update(self, voice_library: VoiceLibrary): + log.debug("Voice library updated - refreshing narrator voice choices") + self.actions["_config"].config[ + "narrator_voice_id" + ].choices = self.narrator_voice_id_choices(self) + await self.emit_status() + + async def on_game_loop_new_message(self, emission: GameLoopNewMessageEvent): + """ + Called when a conversation is generated + """ + + if self.scene.environment == "creative": + return + + character: Character | None = None + + if not self.enabled or not self.ready: + return + + if not isinstance( + emission.message, + (CharacterMessage, NarratorMessage, ContextInvestigationMessage), + ): + return + + if ( + isinstance(emission.message, NarratorMessage) + and not self.generate_for_narration + ): + return + + if ( + isinstance(emission.message, ContextInvestigationMessage) + and not self.generate_for_context_investigation + ): + return + + if isinstance(emission.message, CharacterMessage): + if emission.message.source == "player" and not self.generate_for_player: + return + elif emission.message.source == "ai" and not self.generate_for_npc: + return + + character = self.scene.get_character(emission.message.character_name) + + if isinstance(emission.message, CharacterMessage): + character_prefix = emission.message.split(":", 1)[0] + text_to_generate = str(emission.message).replace( + character_prefix + ": ", "" + ) + elif isinstance(emission.message, ContextInvestigationMessage): + character_prefix = "" + text_to_generate = ( + emission.message.message + ) # Use just the message content, not the title prefix + else: + character_prefix = "" + text_to_generate = str(emission.message) + + log.info( + "reactive tts", message=emission.message, character_prefix=character_prefix + ) + + await self.generate( + text_to_generate, + character=character, + message=emission.message, + ) + + async def on_character_voice_changed(self, event: "VoiceChangedEvent"): + log.debug( + "Character voice changed", character=event.character, voice=event.voice + ) + await self.emit_status() + + # voice helpers + + @property + def ready_apis(self) -> list[str]: + """ + Returns a list of apis that are ready + """ + return [api for api in self.apis if self.api_ready(api)] + + @property + def used_apis(self) -> list[str]: + """ + Returns a list of apis that are in use + + The api is in use if it is the narrator voice or if any of the active characters in the scene use a voice from the api. + """ + return [api for api in self.apis if self.api_used(api)] + + def api_enabled(self, api: str) -> bool: + """ + Returns whether the api is currently in the .apis list, which means it is enabled. + """ + return api in self.apis + + def api_ready(self, api: str) -> bool: + """ + Returns whether the api is ready. + + The api must be enabled and configured. + """ + + if not self.api_enabled(api): + return False + + return self.api_configured(api) + + def api_configured(self, api: str) -> bool: + return getattr(self, f"{api}_configured", True) + + def api_used(self, api: str) -> bool: + """ + Returns whether the narrator or any of the active characters in the scene + use a voice from the given api + + Args: + api (str): The api to check + + Returns: + bool: Whether the api is in use + """ + + if self.narrator_voice and self.narrator_voice.provider == api: + return True + + if not getattr(self, "scene", None): + return False + + for character in self.scene.characters: + if not character.voice: + continue + voice = self.voice_library.get_voice(character.voice.id) + if voice and voice.provider == api: + return True + + return False + + def use_ai_assisted_speaker_separation( + self, + text: str, + message: CharacterMessage + | NarratorMessage + | ContextInvestigationMessage + | None, + ) -> bool: + """ + Returns whether the ai assisted speaker separation should be used for the given text. + """ + try: + if not message and '"' not in text: + return False + + if not message and '"' in text: + return self.speaker_separation in ["ai_assisted", "mixed"] + + if message.source == "player": + return False + + if self.speaker_separation == "ai_assisted": + return True + + if ( + isinstance(message, NarratorMessage) + and self.speaker_separation == "mixed" + ): + return True + + return False + except Exception as e: + log.error( + "Error using ai assisted speaker separation", + error=e, + traceback=traceback.format_exc(), + ) + return False + + # tts markup cache + + async def get_tts_markup_cache(self, text: str) -> str | None: + """ + Returns the cached tts markup for the given text. + """ + fp = hash(text) + cached_markup = self.get_scene_state("tts_markup_cache") + if cached_markup and cached_markup.get("fp") == fp: + return cached_markup.get("markup") + return None + + async def set_tts_markup_cache(self, text: str, markup: str): + fp = hash(text) + self.set_scene_states( + tts_markup_cache={ + "fp": fp, + "markup": markup, + } + ) + + # generation + + @set_processing + async def generate( + self, + text: str, + character: Character | None = None, + force_voice: Voice | None = None, + message: CharacterMessage | NarratorMessage | None = None, + ): + """ + Public entry-point for voice generation. + + The actual audio generation happens sequentially inside a single + background queue. If a queue is currently active, we simply append the + new request to it; if not, we create a new queue (with its own unique + id) and start processing. + """ + if not self.enabled or not self.ready or not text: + return + + self.playback_done_event.set() + + summarizer: "SummarizeAgent" = instance.get_agent("summarizer") + + context = GenerationContext(voice_id=self.narrator_voice_id) + character_voice: Voice = force_voice or self.narrator_voice + + if character and character.voice: + voice = character.voice + if voice and self.api_ready(voice.provider): + character_voice = voice + else: + log.warning( + "Character voice not available", + character=character.name, + voice=character.voice, + ) + + log.debug("Voice routing", character=character, voice=character_voice) + + # initial chunking by separating dialogue from exposition + chunks: list[Chunk] = [] + if self.speaker_separation != "none": + if self.use_ai_assisted_speaker_separation(text, message): + markup = await self.get_tts_markup_cache(text) + if not markup: + log.debug("No markup cache found, generating markup") + markup = await summarizer.markup_context_for_tts(text) + await self.set_tts_markup_cache(text, markup) + else: + log.debug("Using markup cache") + # Use the new markup parser for AI-assisted format + dlg_chunks = dialogue_utils.parse_tts_markup(markup) + else: + # Use the original parser for non-AI-assisted format + dlg_chunks = dialogue_utils.separate_dialogue_from_exposition(text) + + for _dlg_chunk in dlg_chunks: + _voice = ( + character_voice + if _dlg_chunk.type == "dialogue" + else self.narrator_voice + ) + + if _dlg_chunk.speaker is not None: + # speaker name has been identified + _character = self.scene.get_character(_dlg_chunk.speaker) + log.debug( + "Identified speaker", + speaker=_dlg_chunk.speaker, + character=_character, + ) + if ( + _character + and _character.voice + and self.api_ready(_character.voice.provider) + ): + log.debug( + "Using character voice", + character=_character.name, + voice=_character.voice, + ) + _voice = _character.voice + + _api: str = _voice.provider if _voice else self.api + chunk = Chunk( + api=_api, + voice=Voice(**_voice.model_dump()), + model=_voice.provider_model, + generate_fn=getattr(self, f"{_api}_generate"), + prepare_fn=getattr(self, f"{_api}_prepare_chunk", None), + character_name=character.name if character else None, + text=[_dlg_chunk.text], + type=_dlg_chunk.type, + message_id=message.id if message else None, + ) + chunks.append(chunk) + else: + _voice = character_voice if character else self.narrator_voice + _api: str = _voice.provider if _voice else self.api + chunks = [ + Chunk( + api=_api, + voice=Voice(**_voice.model_dump()), + model=_voice.provider_model, + generate_fn=getattr(self, f"{_api}_generate"), + prepare_fn=getattr(self, f"{_api}_prepare_chunk", None), + character_name=character.name if character else None, + text=[text], + type="dialogue" if character else "exposition", + message_id=message.id if message else None, + ) + ] + + # second chunking by splitting into chunks of max_generation_length + + for chunk in chunks: + api_chunk_size = getattr(self, f"{chunk.api}_chunk_size", 0) + + log.debug("chunking", api=chunk.api, api_chunk_size=api_chunk_size) + + _text = [] + + max_generation_length = getattr(self, f"{chunk.api}_max_generation_length") + + if api_chunk_size > 0: + max_generation_length = min(max_generation_length, api_chunk_size) + + for _chunk_text in chunk.text: + if len(_chunk_text) <= max_generation_length: + _text.append(_chunk_text) + continue + + _parsed = parse_chunks(_chunk_text) + _joined = rejoin_chunks(_parsed, chunk_size=max_generation_length) + _text.extend(_joined) + + log.debug("chunked for size", before=chunk.text, after=_text) + + chunk.text = _text + + context.chunks = chunks + + # Enqueue each chunk individually for fine-grained interruptibility + async with self._queue_lock: + if self._queue_id is None: + self._queue_id = str(uuid.uuid4()) + + for chunk in context.chunks: + self._generation_queue.append((context, chunk)) + + # Start processing task if needed + if self._queue_task is None or self._queue_task.done(): + self._queue_task = asyncio.create_task( + self._process_queue(self._queue_id) + ) + + log.debug( + "tts queue enqueue", + queue_id=self._queue_id, + total_items=len(self._generation_queue), + ) + + # The caller doesn't need to wait for the queue to finish; it runs in + # the background. We still register the task with Talemate's + # background-processing tracking so that UI can reflect activity. + await self.set_background_processing(self._queue_task) + + # --------------------------------------------------------------------- + # Queue helpers + # --------------------------------------------------------------------- + + async def _process_queue(self, queue_id: str): + """Sequentially processes all GenerationContext objects in the queue. + + Once the last context has been processed the queue state is reset so a + future generation call will create a new queue (and therefore a new + id). The *queue_id* argument allows us to later add cancellation logic + that can target a specific queue instance. + """ + + try: + while True: + async with self._queue_lock: + if not self._generation_queue: + break + + context, chunk = self._generation_queue.popleft() + + log.debug( + "tts queue dequeue", + queue_id=queue_id, + total_items=len(self._generation_queue), + chunk_type=chunk.type, + ) + + # Process outside lock so other coroutines can enqueue + await self._generate_chunk(chunk, context) + except Exception as e: + log.error( + "Error processing queue", error=e, traceback=traceback.format_exc() + ) + finally: + # Clean up queue state after finishing (or on cancellation) + async with self._queue_lock: + if queue_id == self._queue_id: + self._queue_id = None + self._queue_task = None + self._generation_queue.clear() + + # Public helper so external code (e.g. later cancellation UI) can find the current queue id + def current_queue_id(self) -> str | None: + return self._queue_id + + async def _generate_chunk(self, chunk: Chunk, context: GenerationContext): + """Generate audio for a single chunk (all its sub-chunks).""" + + for _chunk in chunk.sub_chunks: + if not _chunk.cleaned_text.strip(): + continue + + emission: VoiceGenerationEmission = VoiceGenerationEmission( + chunk=_chunk, context=context + ) + + if _chunk.prepare_fn: + await async_signals.get("agent.tts.prepare.before").send(emission) + await _chunk.prepare_fn(_chunk) + await async_signals.get("agent.tts.prepare.after").send(emission) + + log.info( + "Generating audio", + api=chunk.api, + text=_chunk.cleaned_text, + parameters=_chunk.voice.parameters, + prepare_fn=_chunk.prepare_fn, + ) + + await async_signals.get("agent.tts.generate.before").send(emission) + try: + emission.wav_bytes = await _chunk.generate_fn(_chunk, context) + except Exception as e: + log.error("Error generating audio", error=e, chunk=_chunk) + continue + await async_signals.get("agent.tts.generate.after").send(emission) + self.play_audio(emission.wav_bytes, chunk.message_id) + await asyncio.sleep(0.1) + + # Deprecated: kept for backward compatibility but no longer used. + async def generate_chunks(self, context: GenerationContext): + for chunk in context.chunks: + await self._generate_chunk(chunk, context) + + def play_audio(self, audio_data, message_id: int | None = None): + # play audio through the websocket (browser) + + audio_data_encoded: str = base64.b64encode(audio_data).decode("utf-8") + + emit( + "audio_queue", + data={"audio_data": audio_data_encoded, "message_id": message_id}, + ) + + self.playback_done_event.set() # Signal that playback is finished + + async def stop_and_clear_queue(self): + """Cancel any ongoing generation and clear the pending queue. + + This is triggered by UI actions that request immediate stop of TTS + synthesis and playback. It cancels the background task (if still + running) and clears all queued items in a thread-safe manner. + """ + async with self._queue_lock: + # Clear all queued items + self._generation_queue.clear() + + # Cancel the background task if it is still running + if self._queue_task and not self._queue_task.done(): + self._queue_task.cancel() + + # Reset queue identifiers/state + self._queue_id = None + self._queue_task = None + + # Ensure downstream components know playback is finished + self.playback_done_event.set() diff --git a/src/talemate/agents/tts/chatterbox.py b/src/talemate/agents/tts/chatterbox.py new file mode 100644 index 00000000..eba949af --- /dev/null +++ b/src/talemate/agents/tts/chatterbox.py @@ -0,0 +1,317 @@ +import os +import functools +import tempfile +import uuid +import asyncio +import structlog +import pydantic + +import torch + + +# Lazy imports for heavy dependencies +def _import_heavy_deps(): + global ta, ChatterboxTTS + import torchaudio as ta + from chatterbox.tts import ChatterboxTTS + + +CUDA_AVAILABLE = torch.cuda.is_available() + +from talemate.agents.base import ( + AgentAction, + AgentActionConfig, + AgentDetail, +) +from talemate.ux.schema import Field + +from .schema import Voice, Chunk, GenerationContext, VoiceProvider, INFO_CHUNK_SIZE +from .voice_library import add_default_voices +from .providers import register, provider +from .util import voice_is_talemate_asset + +log = structlog.get_logger("talemate.agents.tts.chatterbox") + +add_default_voices( + [ + Voice( + label="Eva", + provider="chatterbox", + provider_id="tts/voice/chatterbox/eva.wav", + tags=["female", "calm", "mature", "thoughtful"], + ), + Voice( + label="Lisa", + provider="chatterbox", + provider_id="tts/voice/chatterbox/lisa.wav", + tags=["female", "energetic", "young"], + ), + Voice( + label="Adam", + provider="chatterbox", + provider_id="tts/voice/chatterbox/adam.wav", + tags=["male", "calm", "mature", "thoughtful", "deep"], + ), + Voice( + label="Bradford", + provider="chatterbox", + provider_id="tts/voice/chatterbox/bradford.wav", + tags=["male", "calm", "mature", "thoughtful", "deep"], + ), + Voice( + label="Julia", + provider="chatterbox", + provider_id="tts/voice/chatterbox/julia.wav", + tags=["female", "calm", "mature"], + ), + Voice( + label="Zoe", + provider="chatterbox", + provider_id="tts/voice/chatterbox/zoe.wav", + tags=["female"], + ), + Voice( + label="William", + provider="chatterbox", + provider_id="tts/voice/chatterbox/william.wav", + tags=["male", "young"], + ), + ] +) + +CHATTERBOX_INFO = """ +Chatterbox is a local text to speech model. + +The voice id is the path to the .wav file for the voice. + +The path can be relative to the talemate root directory, and you can put new *.wav samples +in the `tts/voice/chatterbox` directory. It is also ok if you want to load the files from somewhere else as long as the filepath is available to the talemate backend. + +First generation will download the models (2.13GB + 1.06GB). + +Uses about 4GB of VRAM. +""" + + +@register() +class ChatterboxProvider(VoiceProvider): + name: str = "chatterbox" + allow_model_override: bool = False + allow_file_upload: bool = True + upload_file_types: list[str] = ["audio/wav"] + voice_parameters: list[Field] = [ + Field( + name="exaggeration", + type="number", + label="Exaggeration level", + value=0.5, + min=0.25, + max=2.0, + step=0.05, + ), + Field( + name="cfg_weight", + type="number", + label="CFG/Pace", + value=0.5, + min=0.2, + max=1.0, + step=0.1, + ), + Field( + name="temperature", + type="number", + label="Temperature", + value=0.8, + min=0.05, + max=5.0, + step=0.05, + ), + ] + + +class ChatterboxInstance(pydantic.BaseModel): + model: "ChatterboxTTS" + device: str + + class Config: + arbitrary_types_allowed = True + + +class ChatterboxMixin: + """ + Chatterbox agent mixin for local text to speech. + """ + + @classmethod + def add_actions(cls, actions: dict[str, AgentAction]): + actions["_config"].config["apis"].choices.append( + { + "value": "chatterbox", + "label": "Chatterbox (Local)", + "help": "Chatterbox is a local text to speech model.", + } + ) + + actions["chatterbox"] = AgentAction( + enabled=True, + container=True, + icon="mdi-server-outline", + label="Chatterbox", + description="Chatterbox is a local text to speech model.", + config={ + "device": AgentActionConfig( + type="text", + value="cuda" if CUDA_AVAILABLE else "cpu", + label="Device", + choices=[ + {"value": "cpu", "label": "CPU"}, + {"value": "cuda", "label": "CUDA"}, + ], + description="Device to use for TTS", + ), + "chunk_size": AgentActionConfig( + type="number", + min=0, + step=64, + max=2048, + value=256, + label="Chunk size", + note=INFO_CHUNK_SIZE, + ), + }, + ) + return actions + + @property + def chatterbox_configured(self) -> bool: + return True + + @property + def chatterbox_max_generation_length(self) -> int: + return 512 + + @property + def chatterbox_device(self) -> str: + return self.actions["chatterbox"].config["device"].value + + @property + def chatterbox_chunk_size(self) -> int: + return self.actions["chatterbox"].config["chunk_size"].value + + @property + def chatterbox_info(self) -> str: + return CHATTERBOX_INFO + + @property + def chatterbox_agent_details(self) -> dict: + if not self.chatterbox_configured: + return {} + details = {} + + details["chatterbox_device"] = AgentDetail( + icon="mdi-memory", + value=f"Chatterbox: {self.chatterbox_device}", + description="The device to use for Chatterbox", + ).model_dump() + + return details + + def chatterbox_delete_voice(self, voice: Voice): + """ + Remove the voice from the file system. + Only do this if the path is within TALEMATE_ROOT. + """ + + is_talemate_asset, resolved = voice_is_talemate_asset( + voice, provider(voice.provider) + ) + + log.debug( + "chatterbox_delete_voice", + voice_id=voice.provider_id, + is_talemate_asset=is_talemate_asset, + resolved=resolved, + ) + + if not is_talemate_asset: + return + + try: + if resolved.exists() and resolved.is_file(): + resolved.unlink() + log.debug("Deleted chatterbox voice file", path=str(resolved)) + except Exception as e: + log.error( + "Failed to delete chatterbox voice file", error=e, path=str(resolved) + ) + + def _chatterbox_generate_file( + self, + model: "ChatterboxTTS", + text: str, + audio_prompt_path: str, + output_path: str, + **kwargs, + ): + wav = model.generate(text=text, audio_prompt_path=audio_prompt_path, **kwargs) + ta.save(output_path, wav, model.sr) + return output_path + + async def chatterbox_generate( + self, chunk: Chunk, context: GenerationContext + ) -> bytes | None: + chatterbox_instance: ChatterboxInstance | None = getattr( + self, "chatterbox_instance", None + ) + + reload: bool = False + + if not chatterbox_instance: + reload = True + elif chatterbox_instance.device != self.chatterbox_device: + reload = True + + if reload: + log.debug( + "chatterbox - reinitializing tts instance", + device=self.chatterbox_device, + ) + # Lazy import heavy dependencies only when needed + _import_heavy_deps() + + self.chatterbox_instance = ChatterboxInstance( + model=ChatterboxTTS.from_pretrained(device=self.chatterbox_device), + device=self.chatterbox_device, + ) + + model: "ChatterboxTTS" = self.chatterbox_instance.model + + loop = asyncio.get_event_loop() + + voice = chunk.voice + + with tempfile.TemporaryDirectory() as temp_dir: + file_path = os.path.join(temp_dir, f"tts-{uuid.uuid4()}.wav") + + await loop.run_in_executor( + None, + functools.partial( + self._chatterbox_generate_file, + model=model, + text=chunk.cleaned_text, + audio_prompt_path=voice.provider_id, + output_path=file_path, + **voice.parameters, + ), + ) + + with open(file_path, "rb") as f: + return f.read() + + async def chatterbox_prepare_chunk(self, chunk: Chunk): + voice = chunk.voice + P = provider(voice.provider) + exaggeration = P.voice_parameter(voice, "exaggeration") + + voice.parameters["exaggeration"] = exaggeration diff --git a/src/talemate/agents/tts/elevenlabs.py b/src/talemate/agents/tts/elevenlabs.py new file mode 100644 index 00000000..cfdcf26e --- /dev/null +++ b/src/talemate/agents/tts/elevenlabs.py @@ -0,0 +1,248 @@ +import io +from typing import Union + +import structlog + + +# Lazy imports for heavy dependencies +def _import_heavy_deps(): + global AsyncElevenLabs, ApiError + from elevenlabs.client import AsyncElevenLabs + + # Added explicit ApiError import for clearer error handling + from elevenlabs.core.api_error import ApiError + + +from talemate.ux.schema import Action + +from talemate.agents.base import ( + AgentAction, + AgentActionConfig, + AgentDetail, +) +from .schema import Voice, VoiceLibrary, GenerationContext, Chunk, INFO_CHUNK_SIZE +from .voice_library import add_default_voices + +# emit helper to propagate status messages to the UX +from talemate.emit import emit + +log = structlog.get_logger("talemate.agents.tts.elevenlabs") + + +add_default_voices( + [ + Voice( + label="Adam", + provider="elevenlabs", + provider_id="wBXNqKUATyqu0RtYt25i", + tags=["male", "deep"], + ), + Voice( + label="Amy", + provider="elevenlabs", + provider_id="oGn4Ha2pe2vSJkmIJgLQ", + tags=["female"], + ), + ] +) + + +ELEVENLABS_INFO = """ +ElevenLabs is a cloud-based text to speech API. + +To add new voices, head to their voice library at [https://elevenlabs.io/app/voice-library](https://elevenlabs.io/app/voice-library) and note the voice id of the voice you want to use. (Click 'More Actions -> Copy Voice ID') + +**About elevenlabs voices** +Your elevenlabs subscription allows you to maintain a set number of voices (10 for cheapest plan). + +Any voice that you generate audio for is automatically added to your voices at [https://elevenlabs.io/app/voice-lab](https://elevenlabs.io/app/voice-lab). This also happens when you use the "Test" button above. It is recommend testing via their voice library instead. +""" + + +class ElevenLabsMixin: + """ + ElevenLabs TTS agent mixin for cloud-based text to speech. + """ + + @classmethod + def add_actions(cls, actions: dict[str, AgentAction]): + actions["_config"].config["apis"].choices.append( + { + "value": "elevenlabs", + "label": "ElevenLabs", + "help": "ElevenLabs is a cloud-based text to speech model that uses the ElevenLabs API. (API key required)", + } + ) + + actions["elevenlabs"] = AgentAction( + enabled=True, + container=True, + icon="mdi-server-outline", + label="ElevenLabs", + description="ElevenLabs is a cloud-based text to speech API. (API key required and must be set in the Talemate Settings -> Application -> ElevenLabs)", + config={ + "model": AgentActionConfig( + type="text", + value="eleven_flash_v2_5", + label="Model", + description="Model to use for TTS", + choices=[ + { + "value": "eleven_multilingual_v2", + "label": "Eleven Multilingual V2", + }, + {"value": "eleven_flash_v2_5", "label": "Eleven Flash V2.5"}, + {"value": "eleven_turbo_v2_5", "label": "Eleven Turbo V2.5"}, + ], + ), + "chunk_size": AgentActionConfig( + type="number", + min=0, + step=64, + max=2048, + value=0, + label="Chunk size", + note=INFO_CHUNK_SIZE, + ), + }, + ) + + return actions + + @classmethod + def add_voices(cls, voices: dict[str, VoiceLibrary]): + voices["elevenlabs"] = VoiceLibrary(api="elevenlabs", local=True) + + @property + def elevenlabs_chunk_size(self) -> int: + return self.actions["elevenlabs"].config["chunk_size"].value + + @property + def elevenlabs_configured(self) -> bool: + api_key_set = bool(self.elevenlabs_api_key) + model_set = bool(self.elevenlabs_model) + return api_key_set and model_set + + @property + def elevenlabs_not_configured_reason(self) -> str | None: + if not self.elevenlabs_api_key: + return "ElevenLabs API key not set" + if not self.elevenlabs_model: + return "ElevenLabs model not set" + return None + + @property + def elevenlabs_not_configured_action(self) -> Action | None: + if not self.elevenlabs_api_key: + return Action( + action_name="openAppConfig", + arguments=["application", "elevenlabs_api"], + label="Set API Key", + icon="mdi-key", + ) + if not self.elevenlabs_model: + return Action( + action_name="openAgentSettings", + arguments=["tts", "elevenlabs"], + label="Set Model", + icon="mdi-brain", + ) + return None + + @property + def elevenlabs_max_generation_length(self) -> int: + return 1024 + + @property + def elevenlabs_model(self) -> str: + return self.actions["elevenlabs"].config["model"].value + + @property + def elevenlabs_model_choices(self) -> list[str]: + return [ + {"label": choice["label"], "value": choice["value"]} + for choice in self.actions["elevenlabs"].config["model"].choices + ] + + @property + def elevenlabs_info(self) -> str: + return ELEVENLABS_INFO + + @property + def elevenlabs_agent_details(self) -> dict: + details = {} + + if not self.elevenlabs_configured: + details["elevenlabs_api_key"] = AgentDetail( + icon="mdi-key", + value="ElevenLabs API key not set", + description="ElevenLabs API key not set. You can set it in the Talemate Settings -> Application -> ElevenLabs", + color="error", + ).model_dump() + else: + details["elevenlabs_model"] = AgentDetail( + icon="mdi-brain", + value=self.elevenlabs_model, + description="The model to use for ElevenLabs", + ).model_dump() + + return details + + @property + def elevenlabs_api_key(self) -> str: + return self.config.elevenlabs.api_key + + async def elevenlabs_generate( + self, chunk: Chunk, context: GenerationContext, chunk_size: int = 1024 + ) -> Union[bytes, None]: + api_key = self.elevenlabs_api_key + if not api_key: + return + + # Lazy import heavy dependencies only when needed + _import_heavy_deps() + + client = AsyncElevenLabs(api_key=api_key) + + try: + response_async_iter = client.text_to_speech.convert( + text=chunk.cleaned_text, + voice_id=chunk.voice.provider_id, + model_id=chunk.model or self.elevenlabs_model, + ) + + bytes_io = io.BytesIO() + + async for _chunk_bytes in response_async_iter: + if _chunk_bytes: + bytes_io.write(_chunk_bytes) + + return bytes_io.getvalue() + + except ApiError as e: + # Emit detailed status message to the frontend UI + error_message = "ElevenLabs API Error" + try: + # The ElevenLabs ApiError often contains a JSON body with details + detail = e.body.get("detail", {}) if hasattr(e, "body") else {} + error_message = detail.get("message", str(e)) or str(e) + except Exception: + error_message = str(e) + + log.error("ElevenLabs API error", error=str(e)) + emit( + "status", + message=f"ElevenLabs TTS: {error_message}", + status="error", + ) + raise e + + except Exception as e: + # Catch-all to ensure the app does not crash on unexpected errors + log.error("ElevenLabs TTS generation error", error=str(e)) + emit( + "status", + message=f"ElevenLabs TTS: Unexpected error – {str(e)}", + status="error", + ) + raise e diff --git a/src/talemate/agents/tts/f5tts.py b/src/talemate/agents/tts/f5tts.py new file mode 100644 index 00000000..930d0409 --- /dev/null +++ b/src/talemate/agents/tts/f5tts.py @@ -0,0 +1,436 @@ +import os +import functools +import tempfile +import uuid +import asyncio +import structlog +import pydantic +import re + +import torch + + +# Lazy imports for heavy dependencies +def _import_heavy_deps(): + global F5TTS + from f5_tts.api import F5TTS + + +CUDA_AVAILABLE = torch.cuda.is_available() + +from talemate.agents.base import ( + AgentAction, + AgentActionConfig, + AgentDetail, +) +from talemate.ux.schema import Field + +from .schema import Voice, Chunk, GenerationContext, VoiceProvider, INFO_CHUNK_SIZE +from .voice_library import add_default_voices +from .providers import register, provider +from .util import voice_is_talemate_asset + +log = structlog.get_logger("talemate.agents.tts.f5tts") + +REF_TEXT = "You awaken aboard your ship, the Starlight Nomad. A soft hum resonates throughout the vessel indicating its systems are online." + +add_default_voices( + [ + Voice( + label="Adam", + provider="f5tts", + provider_id="tts/voice/f5tts/adam.wav", + tags=["male", "calm", "mature", "deep", "thoughtful"], + parameters={ + "speed": 1.05, + "ref_text": REF_TEXT, + }, + ), + Voice( + label="Bradford", + provider="f5tts", + provider_id="tts/voice/f5tts/bradford.wav", + tags=["male", "calm", "mature"], + parameters={ + "speed": 1, + "ref_text": REF_TEXT, + }, + ), + Voice( + label="Julia", + provider="f5tts", + provider_id="tts/voice/f5tts/julia.wav", + tags=["female", "calm", "mature"], + parameters={ + "speed": 1.1, + "ref_text": REF_TEXT, + }, + ), + Voice( + label="Lisa", + provider="f5tts", + provider_id="tts/voice/f5tts/lisa.wav", + tags=["female", "young", "energetic"], + parameters={ + "speed": 1.2, + "ref_text": REF_TEXT, + }, + ), + Voice( + label="Eva", + provider="f5tts", + provider_id="tts/voice/f5tts/eva.wav", + tags=["female", "mature", "thoughtful"], + parameters={ + "speed": 1.15, + "ref_text": REF_TEXT, + }, + ), + Voice( + label="Zoe", + provider="f5tts", + provider_id="tts/voice/f5tts/zoe.wav", + tags=["female"], + parameters={ + "speed": 1.15, + "ref_text": REF_TEXT, + }, + ), + Voice( + label="William", + provider="f5tts", + provider_id="tts/voice/f5tts/william.wav", + tags=["male", "young"], + parameters={ + "speed": 1.15, + "ref_text": REF_TEXT, + }, + ), + ] +) + +F5TTS_INFO = """ +F5-TTS is a local text-to-speech model. + +The voice id is the path to the reference *.wav* file that contains a short +voice sample (≈3-5 s). You can place new samples in the +`tts/voice/f5tts` directory of your Talemate workspace or supply an absolute +path that is accessible to the backend. + +The first generation will download the model weights (~1.3 GB) if they are not +cached yet. +""" + + +@register() +class F5TTSProvider(VoiceProvider): + """Metadata for the F5-TTS provider.""" + + name: str = "f5tts" + allow_model_override: bool = False + allow_file_upload: bool = True + upload_file_types: list[str] = ["audio/wav"] + + # Provider-specific tunable parameters that can be stored per-voice + voice_parameters: list[Field] = [ + Field( + name="speed", + type="number", + label="Speed", + value=1.0, + min=0.25, + max=2.0, + step=0.05, + description="If the speech is too fast or slow, adjust this value. 1.0 is normal speed.", + ), + Field( + name="ref_text", + type="text", + label="Reference text", + value="", + description="Text that matches the reference audio sample (improves synthesis quality).", + required=True, + ), + Field( + name="cfg_strength", + type="number", + label="CFG Strength", + value=2.0, + min=0.1, + step=0.1, + max=10.0, + description="CFG strength for the model.", + ), + ] + + +class F5TTSInstance(pydantic.BaseModel): + """Holds a single F5-TTS model instance (lazy-initialised).""" + + model: "F5TTS" # Forward reference for lazy loading + model_name: str + + class Config: + arbitrary_types_allowed = True + + +class F5TTSMixin: + """F5-TTS agent mixin for local text-to-speech generation.""" + + # --------------------------------------------------------------------- + # UI integration / configuration helpers + # --------------------------------------------------------------------- + + @classmethod + def add_actions(cls, actions: dict[str, AgentAction]): + """Expose the F5-TTS backend in the global TTS agent settings.""" + + actions["_config"].config["apis"].choices.append( + { + "value": "f5tts", + "label": "F5-TTS (Local)", + "help": "F5-TTS is a local text-to-speech model.", + } + ) + + actions["f5tts"] = AgentAction( + enabled=True, + container=True, + icon="mdi-server-outline", + label="F5-TTS", + description="F5-TTS is a local text-to-speech model.", + config={ + "device": AgentActionConfig( + type="text", + value="cuda" if CUDA_AVAILABLE else "cpu", + label="Device", + choices=[ + {"value": "cpu", "label": "CPU"}, + {"value": "cuda", "label": "CUDA"}, + ], + description="Device to use for TTS", + ), + "model_name": AgentActionConfig( + type="text", + value="F5TTS_v1_Base", + label="Model", + description="Model will be downloaded on first use.", + choices=[ + {"value": "E2TTS_Base", "label": "E2TTS_Base"}, + {"value": "F5TTS_Base", "label": "F5TTS_Base"}, + {"value": "F5TTS_v1_Base", "label": "F5TTS_v1_Base"}, + ], + ), + "nfe_step": AgentActionConfig( + type="number", + label="NFE Step", + value=32, + min=32, + step=16, + max=64, + description="Number of diffusion steps.", + ), + "chunk_size": AgentActionConfig( + type="number", + min=0, + step=32, + max=1024, + value=64, + label="Chunk size", + note=INFO_CHUNK_SIZE, + ), + "replace_exclamation_marks": AgentActionConfig( + type="bool", + value=True, + label="Replace exclamation marks", + description="Some models tend to over-emphasise exclamation marks, so this is a workaround to make the speech more natural.", + ), + }, + ) + + # No additional per-API settings (model/device) required for F5-TTS. + return actions + + # ------------------------------------------------------------------ + # Convenience properties consumed by the core TTS agent + # ------------------------------------------------------------------ + + @property + def f5tts_configured(self) -> bool: + # Local backend – always available once the model weights are present. + return True + + @property + def f5tts_device(self) -> str: + return self.actions["f5tts"].config["device"].value + + @property + def f5tts_chunk_size(self) -> int: + return self.actions["f5tts"].config["chunk_size"].value + + @property + def f5tts_replace_exclamation_marks(self) -> bool: + return self.actions["f5tts"].config["replace_exclamation_marks"].value + + @property + def f5tts_model_name(self) -> str: + return self.actions["f5tts"].config["model_name"].value + + @property + def f5tts_nfe_step(self) -> int: + return self.actions["f5tts"].config["nfe_step"].value + + @property + def f5tts_max_generation_length(self) -> int: + return 1024 + + @property + def f5tts_info(self) -> str: + return F5TTS_INFO + + @property + def f5tts_agent_details(self) -> dict: + if not self.f5tts_configured: + return {} + details = {} + + device = self.f5tts_device + model_name = self.f5tts_model_name + + details["f5tts_device"] = AgentDetail( + icon="mdi-memory", + value=f"{model_name}@{device}", + description="The model and device to use for F5-TTS", + ).model_dump() + + return details + + # ------------------------------------------------------------------ + # Voice housekeeping helpers + # ------------------------------------------------------------------ + + def f5tts_delete_voice(self, voice: Voice): + """Delete *voice* reference file if it is inside the Talemate workspace.""" + + is_talemate_asset, resolved = voice_is_talemate_asset( + voice, provider(voice.provider) + ) + + log.debug( + "f5tts_delete_voice", + voice_id=voice.provider_id, + is_talemate_asset=is_talemate_asset, + resolved=resolved, + ) + + if not is_talemate_asset: + return + + try: + if resolved.exists() and resolved.is_file(): + resolved.unlink() + log.debug("Deleted F5-TTS voice file", path=str(resolved)) + except Exception as e: + log.error("Failed to delete F5-TTS voice file", error=e, path=str(resolved)) + + # ------------------------------------------------------------------ + # Generation helpers + # ------------------------------------------------------------------ + + def _f5tts_generate_file( + self, + model: "F5TTS", + chunk: Chunk, + voice: Voice, + output_path: str, + ) -> str: + """Blocking generation helper executed in a thread-pool.""" + + wav, sr, _ = model.infer( + ref_file=voice.provider_id, + ref_text=voice.parameters.get("ref_text", ""), + gen_text=chunk.cleaned_text, + file_wave=output_path, + speed=voice.parameters.get("speed", 1.0), + cfg_strength=voice.parameters.get("cfg_strength", 2.0), + nfe_step=self.f5tts_nfe_step, + ) + + # Some versions of F5-TTS don’t write *file_wave*. Drop-in save as fallback. + # if not os.path.exists(output_path): + # ta.save(output_path, wav, sr) + + return output_path + + async def f5tts_generate( + self, chunk: Chunk, context: GenerationContext + ) -> bytes | None: + """Asynchronously synthesise *chunk* using F5-TTS.""" + + # Lazy initialisation & caching across invocations + f5tts_instance: "F5TTSInstance | None" = getattr(self, "f5tts_instance", None) + + device = self.f5tts_device + model_name: str = self.f5tts_model_name + + reload_model = ( + f5tts_instance is None + or f5tts_instance.model.device != device + or f5tts_instance.model_name != model_name + ) + + if reload_model: + if f5tts_instance is not None: + log.debug( + "Reloading F5-TTS backend", device=device, model_name=model_name + ) + else: + log.debug( + "Initialising F5-TTS backend", device=device, model_name=model_name + ) + + # Lazy import heavy dependencies only when needed + _import_heavy_deps() + + f5tts_instance = F5TTSInstance( + model=F5TTS(device=device, model=model_name), + model_name=model_name, + ) + self.f5tts_instance = f5tts_instance + + model: "F5TTS" = f5tts_instance.model + + loop = asyncio.get_event_loop() + + voice = chunk.voice + + with tempfile.TemporaryDirectory() as temp_dir: + file_path = os.path.join(temp_dir, f"tts-{uuid.uuid4()}.wav") + + # Delegate blocking work to the default ThreadPoolExecutor + await loop.run_in_executor( + None, + functools.partial( + self._f5tts_generate_file, model, chunk, voice, file_path + ), + ) + + # Read the generated WAV and return bytes for websocket playback + with open(file_path, "rb") as f: + return f.read() + + async def f5tts_prepare_chunk(self, chunk: Chunk): + text = chunk.text[0] + + # f5-tts seems to have issues with ellipses + text = text.replace("…", "...").replace("...", ".") + + # hyphanated words also seem to be a problem + text = re.sub(r"(\w)-(\w)", r"\1 \2", text) + + if self.f5tts_replace_exclamation_marks: + text = text.replace("!", ".") + + chunk.text[0] = text + + return chunk diff --git a/src/talemate/agents/tts/google.py b/src/talemate/agents/tts/google.py new file mode 100644 index 00000000..f36dfca5 --- /dev/null +++ b/src/talemate/agents/tts/google.py @@ -0,0 +1,319 @@ +import io +import wave +from typing import Union, Optional + +import structlog +from google import genai +from google.genai import types +from talemate.ux.schema import Action +from talemate.agents.base import ( + AgentAction, + AgentActionConfig, + AgentDetail, +) +from .schema import Voice, VoiceLibrary, Chunk, GenerationContext, INFO_CHUNK_SIZE +from .voice_library import add_default_voices + +log = structlog.get_logger("talemate.agents.tts.google") + +GOOGLE_INFO = """ +Google Gemini TTS is a cloud-based text to speech model. + +A list of available voices can be found at [https://ai.google.dev/gemini-api/docs/speech-generation](https://ai.google.dev/gemini-api/docs/speech-generation). +""" + +add_default_voices( + [ + Voice(label="Zephyr", provider="google", provider_id="Zephyr", tags=["female"]), + Voice(label="Puck", provider="google", provider_id="Puck", tags=["male"]), + Voice(label="Charon", provider="google", provider_id="Charon", tags=["male"]), + Voice(label="Kore", provider="google", provider_id="Kore", tags=["female"]), + Voice(label="Fenrir", provider="google", provider_id="Fenrir", tags=["male"]), + Voice(label="Leda", provider="google", provider_id="Leda", tags=["female"]), + Voice(label="Orus", provider="google", provider_id="Orus", tags=["male"]), + Voice(label="Aoede", provider="google", provider_id="Aoede", tags=["female"]), + Voice( + label="Callirrhoe", + provider="google", + provider_id="Callirrhoe", + tags=["female"], + ), + Voice( + label="Autonoe", provider="google", provider_id="Autonoe", tags=["female"] + ), + Voice( + label="Enceladus", + provider="google", + provider_id="Enceladus", + tags=["male", "deep"], + ), + Voice(label="Iapetus", provider="google", provider_id="Iapetus", tags=["male"]), + Voice(label="Umbriel", provider="google", provider_id="Umbriel", tags=["male"]), + Voice( + label="Algieba", + provider="google", + provider_id="Algieba", + tags=["male", "deep"], + ), + Voice( + label="Despina", + provider="google", + provider_id="Despina", + tags=["female", "young"], + ), + Voice( + label="Erinome", provider="google", provider_id="Erinome", tags=["female"] + ), + Voice(label="Algenib", provider="google", provider_id="Algenib", tags=["male"]), + Voice( + label="Rasalgethi", + provider="google", + provider_id="Rasalgethi", + tags=["male", "neutral"], + ), + Voice( + label="Laomedeia", + provider="google", + provider_id="Laomedeia", + tags=["female"], + ), + Voice( + label="Achernar", + provider="google", + provider_id="Achernar", + tags=["female", "young"], + ), + Voice(label="Alnilam", provider="google", provider_id="Alnilam", tags=["male"]), + Voice(label="Schedar", provider="google", provider_id="Schedar", tags=["male"]), + Voice( + label="Gacrux", + provider="google", + provider_id="Gacrux", + tags=["female", "mature"], + ), + Voice( + label="Pulcherrima", + provider="google", + provider_id="Pulcherrima", + tags=["female", "mature"], + ), + Voice( + label="Achird", + provider="google", + provider_id="Achird", + tags=["male", "energetic"], + ), + Voice( + label="Zubenelgenubi", + provider="google", + provider_id="Zubenelgenubi", + tags=["male"], + ), + Voice( + label="Vindemiatrix", + provider="google", + provider_id="Vindemiatrix", + tags=["female", "mature"], + ), + Voice( + label="Sadachbia", provider="google", provider_id="Sadachbia", tags=["male"] + ), + Voice( + label="Sadaltager", + provider="google", + provider_id="Sadaltager", + tags=["male"], + ), + Voice( + label="Sulafat", + provider="google", + provider_id="Sulafat", + tags=["female", "young"], + ), + ] +) + + +class GoogleMixin: + """Google Gemini TTS mixin (Flash/Pro preview models).""" + + @classmethod + def add_actions(cls, actions: dict[str, AgentAction]): + actions["_config"].config["apis"].choices.append( + { + "value": "google", + "label": "Google Gemini", + "help": "Google Gemini is a cloud-based text to speech model that uses the Google Gemini API. (API key required)", + } + ) + + actions["google"] = AgentAction( + enabled=True, + container=True, + icon="mdi-server-outline", + label="Google Gemini", + description="Google Gemini is a cloud-based text to speech API. (API key required and must be set in the Talemate Settings -> Application -> Google)", + config={ + "model": AgentActionConfig( + type="text", + value="gemini-2.5-flash-preview-tts", + choices=[ + { + "value": "gemini-2.5-flash-preview-tts", + "label": "Gemini 2.5 Flash TTS (Preview)", + }, + { + "value": "gemini-2.5-pro-preview-tts", + "label": "Gemini 2.5 Pro TTS (Preview)", + }, + ], + label="Model", + description="Google TTS model to use", + ), + "chunk_size": AgentActionConfig( + type="number", + min=0, + step=64, + max=2048, + value=0, + label="Chunk size", + note=INFO_CHUNK_SIZE, + ), + }, + ) + + return actions + + @classmethod + def add_voices(cls, voices: dict[str, VoiceLibrary]): + voices["google"] = VoiceLibrary(api="google") + + @property + def google_configured(self) -> bool: + return bool(self.google_api_key) and bool(self.google_model) + + @property + def google_chunk_size(self) -> int: + return self.actions["google"].config["chunk_size"].value + + @property + def google_not_configured_reason(self) -> str | None: + if not self.google_api_key: + return "Google API key not set" + if not self.google_model: + return "Google model not set" + return None + + @property + def google_not_configured_action(self) -> Action | None: + if not self.google_api_key: + return Action( + action_name="openAppConfig", + arguments=["application", "google_api"], + label="Set API Key", + icon="mdi-key", + ) + if not self.google_model: + return Action( + action_name="openAgentSettings", + arguments=["tts", "google"], + label="Set Model", + icon="mdi-brain", + ) + return None + + @property + def google_info(self) -> str: + return GOOGLE_INFO + + @property + def google_max_generation_length(self) -> int: + return 1024 + + @property + def google_model(self) -> str: + return self.actions["google"].config["model"].value + + @property + def google_model_choices(self) -> list[str]: + return [ + {"label": choice["label"], "value": choice["value"]} + for choice in self.actions["google"].config["model"].choices + ] + + @property + def google_api_key(self) -> Optional[str]: + return self.config.google.api_key + + @property + def google_agent_details(self) -> dict: + details = {} + + if not self.google_configured: + details["google_api_key"] = AgentDetail( + icon="mdi-key", + value="Google API key not set", + description="Google API key not set. You can set it in the Talemate Settings -> Application -> Google", + color="error", + ).model_dump() + else: + details["google_model"] = AgentDetail( + icon="mdi-brain", + value=self.google_model, + description="The model to use for Google", + ).model_dump() + + return details + + def _make_google_client(self) -> genai.Client: + """Return a fresh genai.Client so updated creds propagate immediately.""" + return genai.Client(api_key=self.google_api_key or None) + + async def google_generate( + self, + chunk: Chunk, + context: GenerationContext, + chunk_size: int = 1024, # kept for signature parity + ) -> Union[bytes, None]: + """Generate audio and wrap raw PCM into a playable WAV container.""" + + voice_name = chunk.voice.provider_id + client = self._make_google_client() + + try: + response = await client.aio.models.generate_content( + model=chunk.model or self.google_model, + contents=chunk.cleaned_text, + config=types.GenerateContentConfig( + response_modalities=["AUDIO"], + speech_config=types.SpeechConfig( + voice_config=types.VoiceConfig( + prebuilt_voice_config=types.PrebuiltVoiceConfig( + voice_name=voice_name, + ) + ) + ), + ), + ) + + # Extract raw 24 kHz 16‑bit PCM (mono) bytes from first candidate + part = response.candidates[0].content.parts[0].inline_data + if not part or not part.data: + return None + pcm_bytes: bytes = part.data + + # Wrap into a WAV container that browsers can decode + wav_io = io.BytesIO() + with wave.open(wav_io, "wb") as wf: + wf.setnchannels(1) + wf.setsampwidth(2) # 16‑bit + wf.setframerate(24000) # Hz + wf.writeframes(pcm_bytes) + return wav_io.getvalue() + + except Exception as e: + import traceback + + traceback.print_exc() + log.error("google_generate failed", error=str(e)) + return None diff --git a/src/talemate/agents/tts/kokoro.py b/src/talemate/agents/tts/kokoro.py new file mode 100644 index 00000000..98b38c17 --- /dev/null +++ b/src/talemate/agents/tts/kokoro.py @@ -0,0 +1,324 @@ +import os +import functools +import tempfile +import uuid +import asyncio +import structlog +import pydantic +import traceback +from pathlib import Path + + +import torch +import soundfile as sf +from kokoro import KPipeline + + +from talemate.agents.base import ( + AgentAction, + AgentActionConfig, +) +from .schema import ( + Voice, + Chunk, + GenerationContext, + VoiceMixer, + VoiceProvider, + INFO_CHUNK_SIZE, +) +from .providers import register +from .voice_library import add_default_voices + +log = structlog.get_logger("talemate.agents.tts.kokoro") + +CUSTOM_VOICE_STORAGE = ( + Path(__file__).parent.parent.parent.parent.parent / "tts" / "voice" / "kokoro" +) + +add_default_voices( + [ + Voice( + label="Alloy", provider="kokoro", provider_id="af_alloy", tags=["female"] + ), + Voice( + label="Aoede", provider="kokoro", provider_id="af_aoede", tags=["female"] + ), + Voice( + label="Bella", provider="kokoro", provider_id="af_bella", tags=["female"] + ), + Voice( + label="Heart", provider="kokoro", provider_id="af_heart", tags=["female"] + ), + Voice( + label="Jessica", + provider="kokoro", + provider_id="af_jessica", + tags=["female"], + ), + Voice(label="Kore", provider="kokoro", provider_id="af_kore", tags=["female"]), + Voice( + label="Nicole", provider="kokoro", provider_id="af_nicole", tags=["female"] + ), + Voice(label="Nova", provider="kokoro", provider_id="af_nova", tags=["female"]), + Voice( + label="River", provider="kokoro", provider_id="af_river", tags=["female"] + ), + Voice( + label="Sarah", provider="kokoro", provider_id="af_sarah", tags=["female"] + ), + Voice(label="Sky", provider="kokoro", provider_id="af_sky", tags=["female"]), + Voice(label="Adam", provider="kokoro", provider_id="am_adam", tags=["male"]), + Voice(label="Echo", provider="kokoro", provider_id="am_echo", tags=["male"]), + Voice(label="Eric", provider="kokoro", provider_id="am_eric", tags=["male"]), + Voice( + label="Fenrir", provider="kokoro", provider_id="am_fenrir", tags=["male"] + ), + Voice(label="Liam", provider="kokoro", provider_id="am_liam", tags=["male"]), + Voice( + label="Michael", provider="kokoro", provider_id="am_michael", tags=["male"] + ), + Voice(label="Onyx", provider="kokoro", provider_id="am_onyx", tags=["male"]), + Voice(label="Puck", provider="kokoro", provider_id="am_puck", tags=["male"]), + Voice(label="Santa", provider="kokoro", provider_id="am_santa", tags=["male"]), + Voice( + label="Alice", provider="kokoro", provider_id="bf_alice", tags=["female"] + ), + Voice(label="Emma", provider="kokoro", provider_id="bf_emma", tags=["female"]), + Voice( + label="Isabella", + provider="kokoro", + provider_id="bf_isabella", + tags=["female"], + ), + Voice(label="Lily", provider="kokoro", provider_id="bf_lily", tags=["female"]), + Voice( + label="Daniel", provider="kokoro", provider_id="bm_daniel", tags=["male"] + ), + Voice(label="Fable", provider="kokoro", provider_id="bm_fable", tags=["male"]), + Voice( + label="George", provider="kokoro", provider_id="bm_george", tags=["male"] + ), + Voice(label="Lewis", provider="kokoro", provider_id="bm_lewis", tags=["male"]), + ] +) + +KOKORO_INFO = """ +Kokoro is a local text to speech model. + +**WILL DOWNLOAD**: Voices will be downloaded on first use, so the first generation will take longer to complete. +""" + + +@register() +class KokoroProvider(VoiceProvider): + name: str = "kokoro" + allow_model_override: bool = False + + +class KokoroInstance(pydantic.BaseModel): + pipeline: "KPipeline" # Forward reference for lazy loading + + class Config: + arbitrary_types_allowed = True + + +class KokoroMixin: + """ + Kokoro agent mixin for local text to speech. + """ + + @classmethod + def add_actions(cls, actions: dict[str, AgentAction]): + actions["_config"].config["apis"].choices.append( + { + "value": "kokoro", + "label": "Kokoro (Local)", + "help": "Kokoro is a local text to speech model.", + } + ) + + actions["kokoro"] = AgentAction( + enabled=True, + container=True, + icon="mdi-server-outline", + label="Kokoro", + description="Kokoro is a local text to speech model.", + config={ + "chunk_size": AgentActionConfig( + type="number", + min=0, + step=64, + max=2048, + value=512, + label="Chunk size", + note=INFO_CHUNK_SIZE, + ), + }, + ) + return actions + + @property + def kokoro_configured(self) -> bool: + return True + + @property + def kokoro_chunk_size(self) -> int: + return self.actions["kokoro"].config["chunk_size"].value + + @property + def kokoro_max_generation_length(self) -> int: + return 256 + + @property + def kokoro_agent_details(self) -> dict: + return {} + + @property + def kokoro_supports_mixing(self) -> bool: + return True + + @property + def kokoro_info(self) -> str: + return KOKORO_INFO + + def kokoro_delete_voice(self, voice_id: str) -> None: + """ + If the voice_id is a file in the CUSTOM_VOICE_STORAGE directory, delete it. + """ + + # if voice id is a deletable file it'll be a relative or absolute path + # to a file in the CUSTOM_VOICE_STORAGE directory + + # we must verify that it is in the CUSTOM_VOICE_STORAGE directory + voice_path = Path(voice_id).resolve() + log.debug( + "Kokoro - Checking if voice id is deletable", + voice_id=voice_id, + exists=voice_path.exists(), + parent=voice_path.parent, + is_custom_voice_storage=voice_path.parent == CUSTOM_VOICE_STORAGE, + ) + if voice_path.exists() and voice_path.parent == CUSTOM_VOICE_STORAGE: + log.debug("Kokoro - Deleting voice file", voice_id=voice_id) + try: + voice_path.unlink() + except FileNotFoundError: + pass + + def _kokoro_mix(self, mixer: VoiceMixer) -> "torch.Tensor": + pipeline = KPipeline(lang_code="a") + + packs = [ + { + "voice_tensor": pipeline.load_single_voice(voice.id), + "weight": voice.weight, + } + for voice in mixer.voices + ] + + mixed_voice = None + for pack in packs: + if mixed_voice is None: + mixed_voice = pack["voice_tensor"] * pack["weight"] + else: + mixed_voice += pack["voice_tensor"] * pack["weight"] + + # TODO: ensure weights sum to 1 + + return mixed_voice + + async def kokoro_test_mix(self, mixer: VoiceMixer): + """Test a mixed voice by generating a sample.""" + mixed_voice_tensor = self._kokoro_mix(mixer) + + loop = asyncio.get_event_loop() + + pipeline = KPipeline(lang_code="a") + + with tempfile.TemporaryDirectory() as temp_dir: + file_path = os.path.join(temp_dir, f"tts-{uuid.uuid4()}.wav") + + await loop.run_in_executor( + None, + functools.partial( + self._kokoro_generate, + pipeline, + "This is a test of the mixed voice.", + mixed_voice_tensor, + file_path, + ), + ) + + # Read and play the audio + with open(file_path, "rb") as f: + audio_data = f.read() + self.play_audio(audio_data) + + async def kokoro_save_mix(self, voice_id: str, mixer: VoiceMixer) -> Path: + """Save a voice tensor to disk.""" + # Ensure the directory exists + CUSTOM_VOICE_STORAGE.mkdir(parents=True, exist_ok=True) + + save_to_path = CUSTOM_VOICE_STORAGE / f"{voice_id}.pt" + voice_tensor = self._kokoro_mix(mixer) + torch.save(voice_tensor, save_to_path) + return save_to_path + + def _kokoro_generate( + self, + pipeline: "KPipeline", + text: str, + voice: "str | torch.Tensor", + file_path: str, + ) -> None: + """Generate audio from text using the given voice.""" + try: + generator = pipeline(text, voice=voice) + for i, (gs, ps, audio) in enumerate(generator): + sf.write(file_path, audio, 24000) + except Exception as e: + traceback.print_exc() + raise e + + async def kokoro_generate( + self, chunk: Chunk, context: GenerationContext + ) -> bytes | None: + kokoro_instance = getattr(self, "kokoro_instance", None) + + reload: bool = False + + if not kokoro_instance: + reload = True + + if reload: + log.debug( + "kokoro - reinitializing tts instance", + ) + # Lazy import heavy dependencies only when needed + + self.kokoro_instance = KokoroInstance( + # a= American English + # TODO: allow config of language??? + pipeline=KPipeline(lang_code="a") + ) + + pipeline = self.kokoro_instance.pipeline + + loop = asyncio.get_event_loop() + + with tempfile.TemporaryDirectory() as temp_dir: + file_path = os.path.join(temp_dir, f"tts-{uuid.uuid4()}.wav") + + await loop.run_in_executor( + None, + functools.partial( + self._kokoro_generate, + pipeline, + chunk.cleaned_text, + chunk.voice.provider_id, + file_path, + ), + ) + + with open(file_path, "rb") as f: + return f.read() diff --git a/src/talemate/agents/tts/nodes.py b/src/talemate/agents/tts/nodes.py new file mode 100644 index 00000000..51c152f2 --- /dev/null +++ b/src/talemate/agents/tts/nodes.py @@ -0,0 +1,165 @@ +import structlog +from typing import ClassVar +from talemate.game.engine.nodes.core import ( + GraphState, + PropertyField, + TYPE_CHOICES, + UNRESOLVED, +) +from talemate.game.engine.nodes.registry import register +from talemate.game.engine.nodes.agent import AgentSettingsNode, AgentNode +from talemate.agents.tts.schema import Voice, VoiceLibrary + +TYPE_CHOICES.extend( + [ + "tts/voice", + ] +) + +log = structlog.get_logger("talemate.game.engine.nodes.agents.tts") + + +@register("agents/tts/Settings") +class TTSAgentSettings(AgentSettingsNode): + """ + Base node to render TTS agent settings. + """ + + _agent_name: ClassVar[str] = "tts" + + def __init__(self, title="TTS Agent Settings", **kwargs): + super().__init__(title=title, **kwargs) + + +@register("agents/tts/GetVoice") +class GetVoice(AgentNode): + """ + Gets a voice from the TTS agent. + """ + + _agent_name: ClassVar[str] = "tts" + + class Fields: + voice_id = PropertyField( + name="voice_id", + type="str", + description="The ID of the voice to get", + default=UNRESOLVED, + ) + + def __init__(self, title="Get Voice", **kwargs): + super().__init__(title=title, **kwargs) + + @property + def voice_library(self) -> VoiceLibrary: + return self.agent.voice_library + + def setup(self): + self.add_input("voice_id", socket_type="str", optional=True) + self.set_property("voice_id", UNRESOLVED) + + self.add_output("voice", socket_type="tts/voice") + + async def run(self, state: GraphState): + voice_id = self.require_input("voice_id") + + voice = self.voice_library.get_voice(voice_id) + + self.set_output_values({"voice": voice}) + + +@register("agents/tts/GetNarratorVoice") +class GetNarratorVoice(AgentNode): + """ + Gets the narrator voice from the TTS agent. + """ + + _agent_name: ClassVar[str] = "tts" + + def __init__(self, title="Get Narrator Voice", **kwargs): + super().__init__(title=title, **kwargs) + + def setup(self): + self.add_output("voice", socket_type="tts/voice") + + async def run(self, state: GraphState): + voice = self.agent.narrator_voice + + self.set_output_values({"voice": voice}) + + +@register("agents/tts/UnpackVoice") +class UnpackVoice(AgentNode): + """ + Unpacks a voice from the TTS agent. + """ + + _agent_name: ClassVar[str] = "tts" + + def __init__(self, title="Unpack Voice", **kwargs): + super().__init__(title=title, **kwargs) + + def setup(self): + self.add_input("voice", socket_type="tts/voice") + self.add_output("voice", socket_type="tts/voice") + self.add_output("label", socket_type="str") + self.add_output("provider", socket_type="str") + self.add_output("provider_id", socket_type="str") + self.add_output("provider_model", socket_type="str") + self.add_output("tags", socket_type="list") + self.add_output("parameters", socket_type="dict") + self.add_output("is_scene_asset", socket_type="bool") + + async def run(self, state: GraphState): + voice: Voice = self.require_input("voice") + + self.set_output_values( + { + "voice": voice, + **voice.model_dump(), + } + ) + + +@register("agents/tts/Generate") +class Generate(AgentNode): + """ + Generates a voice from the TTS agent. + """ + + _agent_name: ClassVar[str] = "tts" + + class Fields: + text = PropertyField( + name="text", + type="text", + description="The text to generate", + default=UNRESOLVED, + ) + + def __init__(self, title="Generate TTS", **kwargs): + super().__init__(title=title, **kwargs) + + def setup(self): + self.add_input("state") + self.add_input("text", socket_type="text", optional=True) + self.add_input("voice", socket_type="tts/voice", optional=True) + self.add_input("character", socket_type="character", optional=True) + self.set_property("text", UNRESOLVED) + self.add_output("state") + + async def run(self, state: GraphState): + text = self.require_input("text") + voice = self.normalized_input_value("voice") + character = self.normalized_input_value("character") + + if not voice and not character: + raise ValueError("Either voice or character must be provided") + + await self.agent.generate( + text=text, + character=character, + force_voice=voice, + ) + + self.set_output_values({"state": state}) diff --git a/src/talemate/agents/tts/openai.py b/src/talemate/agents/tts/openai.py new file mode 100644 index 00000000..1f60c8ae --- /dev/null +++ b/src/talemate/agents/tts/openai.py @@ -0,0 +1,230 @@ +import io +from typing import Union + +import structlog +from openai import AsyncOpenAI +from talemate.ux.schema import Action +from talemate.agents.base import AgentAction, AgentActionConfig, AgentDetail +from .schema import Voice, VoiceLibrary, Chunk, GenerationContext, INFO_CHUNK_SIZE +from .voice_library import add_default_voices + +log = structlog.get_logger("talemate.agents.tts.openai") + +OPENAI_INFO = """ +OpenAI TTS is a cloud-based text to speech model. + +A list of available voices can be found at [https://platform.openai.com/docs/guides/text-to-speech#voice-options](https://platform.openai.com/docs/guides/text-to-speech#voice-options). +""" + +add_default_voices( + [ + Voice( + label="Alloy", + provider="openai", + provider_id="alloy", + tags=["neutral", "female"], + ), + Voice( + label="Ash", + provider="openai", + provider_id="ash", + tags=["male"], + ), + Voice( + label="Ballad", + provider="openai", + provider_id="ballad", + tags=["male", "energetic"], + ), + Voice( + label="Coral", + provider="openai", + provider_id="coral", + tags=["female", "energetic"], + ), + Voice( + label="Echo", + provider="openai", + provider_id="echo", + tags=["male", "neutral"], + ), + Voice( + label="Fable", + provider="openai", + provider_id="fable", + tags=["neutral", "feminine"], + ), + Voice( + label="Onyx", + provider="openai", + provider_id="onyx", + tags=["male"], + ), + Voice( + label="Nova", + provider="openai", + provider_id="nova", + tags=["female"], + ), + Voice( + label="Sage", + provider="openai", + provider_id="sage", + tags=["female"], + ), + Voice( + label="Shimmer", + provider="openai", + provider_id="shimmer", + tags=["female"], + ), + ] +) + + +class OpenAIMixin: + """ + OpenAI TTS agent mixin for cloud-based text to speech. + """ + + @classmethod + def add_actions(cls, actions: dict[str, AgentAction]): + actions["_config"].config["apis"].choices.append( + { + "value": "openai", + "label": "OpenAI", + "help": "OpenAI is a cloud-based text to speech model that uses the OpenAI API. (API key required)", + } + ) + + actions["openai"] = AgentAction( + enabled=True, + container=True, + icon="mdi-server-outline", + label="OpenAI", + description="OpenAI TTS is a cloud-based text to speech API. (API key required and must be set in the Talemate Settings -> Application -> OpenAI)", + config={ + "model": AgentActionConfig( + type="text", + value="gpt-4o-mini-tts", + choices=[ + {"value": "gpt-4o-mini-tts", "label": "GPT-4o Mini TTS"}, + {"value": "tts-1", "label": "TTS 1"}, + {"value": "tts-1-hd", "label": "TTS 1 HD"}, + ], + label="Model", + description="TTS model to use", + ), + "chunk_size": AgentActionConfig( + type="number", + min=0, + step=64, + max=2048, + value=512, + label="Chunk size", + note=INFO_CHUNK_SIZE, + ), + }, + ) + + return actions + + @classmethod + def add_voices(cls, voices: dict[str, VoiceLibrary]): + voices["openai"] = VoiceLibrary(api="openai") + + @property + def openai_chunk_size(self) -> int: + return self.actions["openai"].config["chunk_size"].value + + @property + def openai_max_generation_length(self) -> int: + return 1024 + + @property + def openai_model(self) -> str: + return self.actions["openai"].config["model"].value + + @property + def openai_model_choices(self) -> list[str]: + return [ + {"label": choice["label"], "value": choice["value"]} + for choice in self.actions["openai"].config["model"].choices + ] + + @property + def openai_api_key(self) -> str: + return self.config.openai.api_key + + @property + def openai_configured(self) -> bool: + return bool(self.openai_api_key) and bool(self.openai_model) + + @property + def openai_info(self) -> str: + return OPENAI_INFO + + @property + def openai_not_configured_reason(self) -> str | None: + if not self.openai_api_key: + return "OpenAI API key not set" + if not self.openai_model: + return "OpenAI model not set" + return None + + @property + def openai_not_configured_action(self) -> Action | None: + if not self.openai_api_key: + return Action( + action_name="openAppConfig", + arguments=["application", "openai_api"], + label="Set API Key", + icon="mdi-key", + ) + if not self.openai_model: + return Action( + action_name="openAgentSettings", + arguments=["tts", "openai"], + label="Set Model", + icon="mdi-brain", + ) + return None + + @property + def openai_agent_details(self) -> dict: + details = {} + + if not self.openai_configured: + details["openai_api_key"] = AgentDetail( + icon="mdi-key", + value="OpenAI API key not set", + description="OpenAI API key not set. You can set it in the Talemate Settings -> Application -> OpenAI", + color="error", + ).model_dump() + else: + details["openai_model"] = AgentDetail( + icon="mdi-brain", + value=self.openai_model, + description="The model to use for OpenAI", + ).model_dump() + + return details + + async def openai_generate( + self, chunk: Chunk, context: GenerationContext, chunk_size: int = 1024 + ) -> Union[bytes, None]: + client = AsyncOpenAI(api_key=self.openai_api_key) + + model = chunk.model or self.openai_model + + response = await client.audio.speech.create( + model=model, voice=chunk.voice.provider_id, input=chunk.cleaned_text + ) + + bytes_io = io.BytesIO() + for chunk in response.iter_bytes(chunk_size=chunk_size): + if chunk: + bytes_io.write(chunk) + + # Put the audio data in the queue for playback + return bytes_io.getvalue() diff --git a/src/talemate/agents/tts/providers.py b/src/talemate/agents/tts/providers.py new file mode 100644 index 00000000..367a07e8 --- /dev/null +++ b/src/talemate/agents/tts/providers.py @@ -0,0 +1,24 @@ +from .schema import VoiceProvider +from typing import Generator + +__all__ = ["register", "provider", "providers"] + +PROVIDERS = {} + + +class register: + def __call__(self, cls: type[VoiceProvider]): + PROVIDERS[cls().name] = cls + return cls + + +def provider(name: str) -> VoiceProvider: + cls = PROVIDERS.get(name) + if not cls: + return VoiceProvider(name=name) + return cls() + + +def providers() -> Generator[VoiceProvider, None, None]: + for cls in PROVIDERS.values(): + yield cls() diff --git a/src/talemate/agents/tts/schema.py b/src/talemate/agents/tts/schema.py new file mode 100644 index 00000000..806d2b38 --- /dev/null +++ b/src/talemate/agents/tts/schema.py @@ -0,0 +1,201 @@ +import pydantic +from pathlib import Path +import re +from typing import Callable, Literal + +from talemate.ux.schema import Note, Field +from talemate.path import TALEMATE_ROOT + +__all__ = [ + "APIStatus", + "Chunk", + "GenerationContext", + "VoiceProvider", + "Voice", + "VoiceLibrary", + "VoiceWeight", + "VoiceMixer", + "VoiceGenerationEmission", + "INFO_CHUNK_SIZE", +] + + +MAX_TAG_LENGTH: int = 64 # Maximum number of characters per tag (configurable) +MAX_TAGS_PER_VOICE: int = 10 # Maximum number of tags per voice (configurable) + +DEFAULT_VOICE_DIR = TALEMATE_ROOT / "tts" / "voice" + +INFO_CHUNK_SIZE = "Split text into chunks of this size. Smaller values will increase responsiveness at the cost of lost context between chunks. (Stuff like appropriate inflection, etc.). 0 = no chunking." + + +class VoiceProvider(pydantic.BaseModel): + name: str + voice_parameters: list[Field] = pydantic.Field(default_factory=list) + allow_model_override: bool = True + allow_file_upload: bool = False + upload_file_types: list[str] | None = None + + @property + def default_parameters(self) -> dict[str, str | float | int | bool]: + return {param.name: param.value for param in self.voice_parameters} + + @property + def default_voice_dir(self) -> Path: + return DEFAULT_VOICE_DIR / self.name + + def voice_parameter( + self, voice: "Voice", name: str + ) -> str | float | int | bool | None: + """ + Get a parameter from the voice. + If the parameter is not set, return the default parameter from the provider. + """ + if name in voice.parameters: + return voice.parameters[name] + return self.default_parameters.get(name) + + +class VoiceWeight(pydantic.BaseModel): + id: str + weight: float + + +class VoiceMixer(pydantic.BaseModel): + voices: list[VoiceWeight] + + +class Voice(pydantic.BaseModel): + # arbitrary voice label to allow a human to easily identify the voice + label: str + + # voice provider, this would be the TTS api in the voice + provider: str + + # voice id as known to the voice provider + provider_id: str + + # allows to also override to a specific model + provider_model: str | None = None + + # free-form tags for categorizing the voice (e.g. "male", "energetic") + tags: list[str] = pydantic.Field(default_factory=list) + + # provider specific parameters for the voice + parameters: dict[str, str | float | int | bool] = pydantic.Field( + default_factory=dict + ) + + is_scene_asset: bool = False + + @pydantic.field_validator("tags") + @classmethod + def _validate_tags(cls, v: list[str]): + """Validate tag list length and individual tag length.""" + if len(v) > MAX_TAGS_PER_VOICE: + raise ValueError( + f"Too many tags – maximum {MAX_TAGS_PER_VOICE} tags are allowed per voice" + ) + for tag in v: + if len(tag) > MAX_TAG_LENGTH: + raise ValueError( + f"Tag '{tag}' exceeds maximum length of {MAX_TAG_LENGTH} characters" + ) + return v + + model_config = pydantic.ConfigDict(validate_assignment=True, exclude_none=True) + + @pydantic.computed_field(description="The unique identifier for the voice") + @property + def id(self) -> str: + return f"{self.provider}:{self.provider_id}" + + +class VoiceLibrary(pydantic.BaseModel): + version: int = 1 + voices: dict[str, Voice] = pydantic.Field(default_factory=dict) + + def get_voice(self, voice_id: str) -> Voice | None: + return self.voices.get(voice_id) + + +class Chunk(pydantic.BaseModel): + text: list[str] = pydantic.Field(default_factory=list) + type: Literal["dialogue", "exposition"] + character_name: str | None = None + api: str | None = None + voice: Voice | None = None + model: str | None = None + generate_fn: Callable | None = None + prepare_fn: Callable | None = None + message_id: int | None = None + + @property + def cleaned_text(self) -> str: + cleaned: str = self.text[0].replace("*", "").replace('"', "").replace("`", "") + + # troublemakers + cleaned = cleaned.replace("—", " - ").replace("…", "...").replace(";", ",") + + # replace any grouped up whitespace with a single space + cleaned = re.sub(r"\s+", " ", cleaned) + + # replace full uppercase word with lowercase + # e.g. "HELLO" -> "hello" + cleaned = re.sub(r"[A-Z]{2,}", lambda m: m.group(0).lower(), cleaned) + + cleaned = cleaned.strip(",").strip() + + # If there is no commong sentence ending punctuation, add a period + if len(cleaned) > 0 and cleaned[-1] not in [".", "!", "?"]: + cleaned += "." + + return cleaned.strip().strip(",").strip() + + @property + def sub_chunks(self) -> list["Chunk"]: + if len(self.text) == 1: + return [self] + + return [ + Chunk( + text=[text], + type=self.type, + character_name=self.character_name, + api=self.api, + voice=Voice(**self.voice.model_dump()), + model=self.model, + generate_fn=self.generate_fn, + prepare_fn=self.prepare_fn, + ) + for text in self.text + ] + + +class GenerationContext(pydantic.BaseModel): + chunks: list[Chunk] = pydantic.Field(default_factory=list) + + +class VoiceGenerationEmission(pydantic.BaseModel): + chunk: Chunk + context: GenerationContext + wav_bytes: bytes | None = None + + +class ModelChoice(pydantic.BaseModel): + label: str + value: str + + +class APIStatus(pydantic.BaseModel): + """Status of an API.""" + + api: str + enabled: bool + ready: bool + configured: bool + provider: VoiceProvider + messages: list[Note] = pydantic.Field(default_factory=list) + supports_mixing: bool = False + + default_model: str | None = None + model_choices: list[ModelChoice] = pydantic.Field(default_factory=list) diff --git a/src/talemate/agents/tts/util.py b/src/talemate/agents/tts/util.py new file mode 100644 index 00000000..204b0771 --- /dev/null +++ b/src/talemate/agents/tts/util.py @@ -0,0 +1,111 @@ +from pathlib import Path +from typing import TYPE_CHECKING +import structlog + +from .schema import TALEMATE_ROOT, Voice, VoiceProvider + +from .voice_library import get_instance + +if TYPE_CHECKING: + from talemate.tale_mate import Scene + +log = structlog.get_logger("talemate.agents.tts.util") + +__all__ = [ + "voice_parameter", + "voice_is_talemate_asset", + "voice_is_scene_asset", + "get_voice", +] + + +def voice_parameter( + voice: Voice, provider: VoiceProvider, name: str +) -> str | float | int | bool | None: + """ + Get a parameter from the voice. + """ + if name in voice.parameters: + return voice.parameters[name] + return provider.default_parameters.get(name) + + +def voice_is_talemate_asset( + voice: Voice, provider: VoiceProvider +) -> tuple[bool, Path | None]: + """ + Check if the voice is a Talemate asset. + """ + + if not provider.allow_file_upload: + return False, None + + path = Path(voice.provider_id) + if not path.is_absolute(): + path = TALEMATE_ROOT / path + try: + resolved = path.resolve(strict=False) + except Exception as e: + log.error( + "voice_is_talemate_asset - invalid path", + error=e, + voice_id=voice.provider_id, + ) + return False, None + + root = TALEMATE_ROOT.resolve() + log.debug( + "voice_is_talemate_asset - resolved", resolved=str(resolved), root=str(root) + ) + if not str(resolved).startswith(str(root)): + return False, None + + return True, resolved + + +def voice_is_scene_asset(voice: Voice, provider: VoiceProvider) -> bool: + """ + Check if the voice is a scene asset. + + Scene assets are stored in the the scene's assets directory. + + This function does NOT check .is_scene_asset but does path resolution to + determine if the voice is a scene asset. + """ + + is_talemate_asset, resolved = voice_is_talemate_asset(voice, provider) + if not is_talemate_asset: + return False + + SCENES_DIR = TALEMATE_ROOT / "scenes" + + if str(resolved).startswith(str(SCENES_DIR.resolve())): + return True + + return False + + +def get_voice(scene: "Scene", voice_id: str) -> Voice | None: + """Return a Voice by *voice_id* preferring the scene's library (if any). + + Args: + scene: Scene instance or ``None``. + voice_id: The fully-qualified voice identifier (``provider:provider_id``). + + The function first checks *scene.voice_library* (if present) and falls back + to the global voice library instance. + """ + + try: + if scene and getattr(scene, "voice_library", None): + voice = scene.voice_library.get_voice(voice_id) + if voice: + return voice + except Exception as e: + log.error("get_voice - scene lookup failed", error=e) + + try: + return get_instance().get_voice(voice_id) + except Exception as e: + log.error("get_voice - global lookup failed", error=e) + return None diff --git a/src/talemate/agents/tts/voice_library.py b/src/talemate/agents/tts/voice_library.py new file mode 100644 index 00000000..74afb15c --- /dev/null +++ b/src/talemate/agents/tts/voice_library.py @@ -0,0 +1,169 @@ +import structlog +from pathlib import Path # +import pydantic + +import talemate.emit.async_signals as async_signals + +from .schema import VoiceLibrary, Voice +from typing import TYPE_CHECKING, Callable, Literal + +if TYPE_CHECKING: + from talemate.tale_mate import Scene + +__all__ = [ + "load_voice_library", + "save_voice_library", + "get_instance", + "add_default_voices", + "DEFAULT_VOICES", + "VOICE_LIBRARY_PATH", + "require_instance", + "load_scene_voice_library", + "save_scene_voice_library", + "scoped_voice_library", +] + +log = structlog.get_logger("talemate.agents.tts.voice_library") + +async_signals.register( + "voice_library.update.before", + "voice_library.update.after", +) + +VOICE_LIBRARY_PATH = ( + Path(__file__).parent.parent.parent.parent.parent + / "tts" + / "voice" + / "voice-library.json" +) + + +DEFAULT_VOICES = {} + +# TODO: does this need to be made thread safe? +VOICE_LIBRARY = None + + +class ScopedVoiceLibrary(pydantic.BaseModel): + voice_library: VoiceLibrary + fn_save: Callable[[VoiceLibrary], None] + + async def save(self): + await self.fn_save(self.voice_library) + + +def scoped_voice_library( + scope: Literal["global", "scene"], scene: "Scene | None" = None +) -> ScopedVoiceLibrary: + if scope == "global": + return ScopedVoiceLibrary( + voice_library=get_instance(), fn_save=save_voice_library + ) + else: + if not scene: + raise ValueError("Scene is required for scoped voice library") + + async def _save(library: VoiceLibrary): + await save_scene_voice_library(scene, library) + + return ScopedVoiceLibrary(voice_library=scene.voice_library, fn_save=_save) + + +async def require_instance(): + global VOICE_LIBRARY + if not VOICE_LIBRARY: + VOICE_LIBRARY = await load_voice_library() + return VOICE_LIBRARY + + +async def load_voice_library() -> VoiceLibrary: + """ + Load the voice library from the file. + """ + try: + with open(VOICE_LIBRARY_PATH, "r") as f: + return VoiceLibrary.model_validate_json(f.read()) + except FileNotFoundError: + library = VoiceLibrary(voices=DEFAULT_VOICES) + await save_voice_library(library) + return library + finally: + log.debug("loaded voice library", path=str(VOICE_LIBRARY_PATH)) + + +async def save_voice_library(voice_library: VoiceLibrary): + """ + Save the voice library to the file. + """ + await async_signals.get("voice_library.update.before").send(voice_library) + with open(VOICE_LIBRARY_PATH, "w") as f: + f.write(voice_library.model_dump_json(indent=2)) + await async_signals.get("voice_library.update.after").send(voice_library) + + +def get_instance() -> VoiceLibrary: + """ + Get the shared voice library instance. + """ + if not VOICE_LIBRARY: + raise RuntimeError("Voice library not loaded yet.") + return VOICE_LIBRARY + + +def add_default_voices(voices: list[Voice]): + """ + Add default voices to the voice library. + """ + global DEFAULT_VOICES + for voice in voices: + DEFAULT_VOICES[voice.id] = voice + + +def voices_for_apis(apis: list[str], voice_library: VoiceLibrary) -> list[Voice]: + """ + Get the voices for the given apis. + """ + return [voice for voice in voice_library.voices.values() if voice.provider in apis] + + +def _scene_library_path(scene: "Scene") -> Path: + """Return the path to the *scene* voice-library.json file.""" + + return Path(scene.info_dir) / "voice-library.json" + + +async def load_scene_voice_library(scene: "Scene") -> VoiceLibrary: + """Load and return the voice library for *scene*. + + If the file does not exist an empty ``VoiceLibrary`` instance is returned. + The returned instance is *not* stored on the scene – caller decides. + """ + + path = _scene_library_path(scene) + + try: + if path.exists(): + with open(path, "r") as f: + library = VoiceLibrary.model_validate_json(f.read()) + else: + library = VoiceLibrary() + except Exception as e: + log.error("load_scene_voice_library", error=e, path=str(path)) + library = VoiceLibrary() + + return library + + +async def save_scene_voice_library(scene: "Scene", library: VoiceLibrary): + """Persist *library* to the scene's ``voice-library.json``. + + The directory ``scene/{name}/info`` is created if necessary. + """ + + path = _scene_library_path(scene) + try: + path.parent.mkdir(parents=True, exist_ok=True) + with open(path, "w") as f: + f.write(library.model_dump_json(indent=2)) + except Exception as e: + log.error("save_scene_voice_library", error=e, path=str(path)) diff --git a/src/talemate/agents/tts/websocket_handler.py b/src/talemate/agents/tts/websocket_handler.py new file mode 100644 index 00000000..f1133a57 --- /dev/null +++ b/src/talemate/agents/tts/websocket_handler.py @@ -0,0 +1,674 @@ +from __future__ import annotations + +import asyncio +from pathlib import Path +import pydantic +import structlog + + +from typing import TYPE_CHECKING, Literal +import base64 +import os +import re + +import talemate.emit.async_signals as async_signals +from talemate.instance import get_agent +from talemate.server.websocket_plugin import Plugin + +import talemate.scene_message as scene_message + +from .voice_library import ( + get_instance as get_voice_library, + save_voice_library, + scoped_voice_library, + ScopedVoiceLibrary, +) +from .schema import ( + Voice, + GenerationContext, + Chunk, + APIStatus, + VoiceMixer, + VoiceWeight, + TALEMATE_ROOT, + VoiceLibrary, +) + +from .util import voice_is_scene_asset +from .providers import provider + +if TYPE_CHECKING: + from talemate.agents.tts import TTSAgent + from talemate.tale_mate import Scene + from talemate.character import Character + +__all__ = [ + "TTSWebsocketHandler", +] + +log = structlog.get_logger("talemate.server.voice_library") + + +class EditVoicePayload(pydantic.BaseModel): + """Payload for editing an existing voice. Only specified fields are updated.""" + + voice_id: str + scope: Literal["global", "scene"] + + label: str + provider: str + provider_id: str + provider_model: str | None = None + tags: list[str] = pydantic.Field(default_factory=list) + parameters: dict[str, int | float | str | bool] = pydantic.Field( + default_factory=dict + ) + + +class VoiceRefPayload(pydantic.BaseModel): + """Payload referencing an existing voice by its id (used for remove / test).""" + + voice_id: str + scope: Literal["global", "scene"] + + +class TestVoicePayload(pydantic.BaseModel): + """Payload for testing a voice.""" + + provider: str + provider_id: str + provider_model: str | None = None + text: str | None = None + parameters: dict[str, int | float | str | bool] = pydantic.Field( + default_factory=dict + ) + + +class TestCharacterVoicePayload(pydantic.BaseModel): + """Payload for testing a character voice.""" + + character_name: str + text: str | None = None + + +class AddVoicePayload(Voice): + """Explicit payload for adding a new voice - identical fields to Voice.""" + + scope: Literal["global", "scene"] + + +class TestMixedVoicePayload(pydantic.BaseModel): + """Payload for testing a mixed voice.""" + + provider: str + voices: list[VoiceWeight] + + +class SaveMixedVoicePayload(pydantic.BaseModel): + """Payload for saving a mixed voice.""" + + provider: str + label: str + voices: list[VoiceWeight] + tags: list[str] = pydantic.Field(default_factory=list) + + +class UploadVoiceFilePayload(pydantic.BaseModel): + """Payload for uploading a new voice file for providers that support it.""" + + provider: str + label: str + content: str # Base64 data URL (e.g. data:audio/wav;base64,AAAB...) + as_scene_asset: bool = False + + @pydantic.field_validator("content") + @classmethod + def _validate_content(cls, v: str): + if not v.startswith("data:") or ";base64," not in v: + raise ValueError("Content must be a base64 data URL") + return v + + +class GenerateForSceneMessagePayload(pydantic.BaseModel): + """Payload for generating a voice for a scene message.""" + + message_id: int | Literal["intro"] + + +class TTSWebsocketHandler(Plugin): + """Websocket plugin to manage the TTS voice library.""" + + router = "tts" + + def __init__(self, websocket_handler): + super().__init__(websocket_handler) + # Immediately send current voice list to the frontend + asyncio.create_task(self._send_voice_list()) + + # --------------------------------------------------------------------- + # Events + # --------------------------------------------------------------------- + + def connect(self): + # needs to be after config is saved so the TTS agent has already + # refreshed to the latest config + async_signals.get("config.changed.follow").connect( + self.on_app_config_change_followup + ) + + async def on_app_config_change_followup(self, event): + self._send_api_status() + + # --------------------------------------------------------------------- + # Helper methods + # --------------------------------------------------------------------- + + async def _send_voice_list(self, select_voice_id: str | None = None): + # global voice library + voice_library = get_voice_library() + voices = [v.model_dump() for v in voice_library.voices.values()] + voices.sort(key=lambda x: x["label"]) + + # scene voice library + if self.scene: + scene_voice_library = self.scene.voice_library + scene_voices = [v.model_dump() for v in scene_voice_library.voices.values()] + scene_voices.sort(key=lambda x: x["label"]) + else: + scene_voices = [] + + self.websocket_handler.queue_put( + { + "type": self.router, + "action": "voices", + "voices": voices, + "scene_voices": scene_voices, + "select_voice_id": select_voice_id, + } + ) + + def _voice_exists(self, voice_library: VoiceLibrary, voice_id: str) -> bool: + return voice_id in voice_library.voices + + def _broadcast_update(self, select_voice_id: str | None = None): + # After any mutation we broadcast the full list for simplicity + asyncio.create_task(self._send_voice_list(select_voice_id)) + + def _send_api_status(self): + tts_agent: "TTSAgent" = get_agent("tts") + api_status: list[APIStatus] = tts_agent.api_status + self.websocket_handler.queue_put( + { + "type": self.router, + "action": "api_status", + "api_status": [s.model_dump() for s in api_status], + } + ) + + # --------------------------------------------------------------------- + # Handlers + # --------------------------------------------------------------------- + + async def handle_list(self, data: dict): + await self._send_voice_list() + + async def handle_api_status(self, data: dict): + self._send_api_status() + + async def handle_add(self, data: dict): + try: + voice = AddVoicePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + if voice.scope == "scene" and not self.scene: + await self.signal_operation_failed("No scene active") + return + + scoped: ScopedVoiceLibrary = scoped_voice_library(voice.scope, self.scene) + voice.is_scene_asset = voice.scope == "scene" + + if self._voice_exists(scoped.voice_library, voice.id): + await self.signal_operation_failed("Voice already exists") + return + + scoped.voice_library.voices[voice.id] = voice + + await scoped.save() + + self._broadcast_update() + await self.signal_operation_done() + + async def handle_remove(self, data: dict): + try: + payload = VoiceRefPayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + tts_agent: "TTSAgent" = get_agent("tts") + + scoped: ScopedVoiceLibrary = scoped_voice_library(payload.scope, self.scene) + + log.debug("Removing voice", voice_id=payload.voice_id, scope=payload.scope) + + try: + voice = scoped.voice_library.voices.pop(payload.voice_id) + except KeyError: + await self.signal_operation_failed("Voice not found (1)") + return + + provider = voice.provider + # check if porivder has a delete method + delete_method = getattr(tts_agent, f"{provider}_delete_voice", None) + if delete_method: + delete_method(voice) + + await scoped.save() + self._broadcast_update() + await self.signal_operation_done() + + async def handle_edit(self, data: dict): + try: + payload = EditVoicePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + scoped: ScopedVoiceLibrary = scoped_voice_library(payload.scope, self.scene) + voice = scoped.voice_library.voices.get(payload.voice_id) + if not voice: + await self.signal_operation_failed("Voice not found") + return + + # all fields are always provided + voice.label = payload.label + voice.provider = payload.provider + voice.provider_id = payload.provider_id + voice.provider_model = payload.provider_model + voice.tags = payload.tags + voice.parameters = payload.parameters + voice.is_scene_asset = voice_is_scene_asset(voice, provider(voice.provider)) + + # If provider or provider_id changed, id changes -> reinsert + new_id = voice.id + if new_id != payload.voice_id: + # Remove old key, insert new + del scoped.voice_library.voices[payload.voice_id] + scoped.voice_library.voices[new_id] = voice + + await scoped.save() + self._broadcast_update() + await self.signal_operation_done() + + async def handle_test(self, data: dict): + """Handle a request to test a voice. + + Supports two payload formats: + + 1. Existing voice - identified by ``voice_id`` (legacy behaviour) + 2. Unsaved voice - identified by at least ``provider`` and ``provider_id``. + """ + + tts_agent: "TTSAgent" = get_agent("tts") + + try: + payload = TestVoicePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + voice = Voice( + label=f"{payload.provider_id} (test)", + provider=payload.provider, + provider_id=payload.provider_id, + provider_model=payload.provider_model, + parameters=payload.parameters, + ) + + if not tts_agent or not tts_agent.api_ready(voice.provider): + await self.signal_operation_failed(f"API '{voice.provider}' not ready") + return + + generate_fn = getattr(tts_agent, f"{voice.provider}_generate", None) + if not generate_fn: + await self.signal_operation_failed("Provider not supported by TTS agent") + return + + prepare_fn = getattr(tts_agent, f"{voice.provider}_prepare_chunk", None) + + # Use provided text or default + test_text = payload.text or "This is a test of the selected voice." + + # Build minimal generation context + context = GenerationContext() + chunk = Chunk( + text=[test_text], + type="dialogue", + api=voice.provider, + voice=voice, + model=voice.provider_model, + generate_fn=generate_fn, + prepare_fn=prepare_fn, + character_name=None, + ) + context.chunks.append(chunk) + + # Run generation in background so we don't block the event loop + async def _run_test(): + try: + await tts_agent.generate_chunks(context) + finally: + await self.signal_operation_done(signal_only=True) + + asyncio.create_task(_run_test()) + + async def handle_test_character_voice(self, data: dict): + """Handle a request to test a character voice.""" + + try: + payload = TestCharacterVoicePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + character = self.scene.get_character(payload.character_name) + if not character: + await self.signal_operation_failed("Character not found") + return + + if not character.voice: + await self.signal_operation_failed("Character has no voice") + return + + text: str = payload.text or "This is a test of the selected voice." + + await self.handle_test( + { + "provider": character.voice.provider, + "provider_id": character.voice.provider_id, + "provider_model": character.voice.provider_model, + "parameters": character.voice.parameters, + "text": text, + } + ) + + async def handle_test_mixed(self, data: dict): + """Handle a request to test a mixed voice.""" + + tts_agent: "TTSAgent" = get_agent("tts") + + try: + payload = TestMixedVoicePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + # Validate that weights sum to 1.0 + total_weight = sum(v.weight for v in payload.voices) + if abs(total_weight - 1.0) > 0.001: + await self.signal_operation_failed( + f"Weights must sum to 1.0, got {total_weight}" + ) + return + + if not tts_agent or not tts_agent.api_ready(payload.provider): + await self.signal_operation_failed(f"{payload.provider} API not ready") + return + + # Build mixer + mixer = VoiceMixer(voices=payload.voices) + + # Run test in background using the appropriate provider's test method + test_method = getattr(tts_agent, f"{payload.provider}_test_mix", None) + if not test_method: + await self.signal_operation_failed( + f"{payload.provider} does not implement voice mixing" + ) + return + + async def _run_test(): + try: + await test_method(mixer) + finally: + await self.signal_operation_done(signal_only=True) + + asyncio.create_task(_run_test()) + + async def handle_save_mixed(self, data: dict): + """Handle a request to save a mixed voice.""" + + tts_agent: "TTSAgent" = get_agent("tts") + + try: + payload = SaveMixedVoicePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + # Validate that weights sum to 1.0 + total_weight = sum(v.weight for v in payload.voices) + if abs(total_weight - 1.0) > 0.001: + await self.signal_operation_failed( + f"Weights must sum to 1.0, got {total_weight}" + ) + return + + if not tts_agent or not tts_agent.api_ready(payload.provider): + await self.signal_operation_failed(f"{payload.provider} API not ready") + return + + # Build mixer + mixer = VoiceMixer(voices=payload.voices) + + # Create a unique voice id for the mixed voice + voice_id = f"{payload.label.lower().replace(' ', '-')}" + + # Mix and save the voice using the appropriate provider's methods + save_method = getattr(tts_agent, f"{payload.provider}_save_mix", None) + + if not save_method: + await self.signal_operation_failed( + f"{payload.provider} does not implement voice mixing" + ) + return + + try: + saved_path = await save_method(voice_id, mixer) + + # voice id is Path relative to talemate root + voice_id = str(saved_path.relative_to(TALEMATE_ROOT)) + + # Add the voice to the library + new_voice = Voice( + label=payload.label, + provider=payload.provider, + provider_id=voice_id, + tags=payload.tags, + mix=mixer, + ) + + voice_library = get_voice_library() + voice_library.voices[new_voice.id] = new_voice + await save_voice_library(voice_library) + self._broadcast_update(new_voice.id) + await self.signal_operation_done() + + except Exception as e: + log.error("Failed to save mixed voice", error=e) + await self.signal_operation_failed(f"Failed to save mixed voice: {str(e)}") + + async def handle_generate_for_scene_message(self, data: dict): + """Handle a request to generate a voice for a scene message.""" + + tts_agent: "TTSAgent" = get_agent("tts") + scene: "Scene" = self.scene + + log.debug("Generating TTS for scene message", data=data) + + try: + payload = GenerateForSceneMessagePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + log.debug("Payload", payload=payload) + + character: "Character | None" = None + text: str = "" + message: scene_message.SceneMessage | None = None + + if payload.message_id == "intro": + text = scene.get_intro() + else: + message = scene.get_message(payload.message_id) + + if not message: + await self.signal_operation_failed("Message not found") + return + + if message.typ not in ["character", "narrator", "context_investigation"]: + await self.signal_operation_failed( + "Message is not a character, narrator, or context investigation message" + ) + return + + log.debug("Message type", message_type=message.typ) + + if isinstance(message, scene_message.CharacterMessage): + character = scene.get_character(message.character_name) + + if not character: + await self.signal_operation_failed("Character not found") + return + + text = message.without_name + elif isinstance(message, scene_message.ContextInvestigationMessage): + text = message.message + else: + text = message.message + + if not text: + await self.signal_operation_failed("No text to generate speech for.") + return + + await tts_agent.generate(text, character, message=message) + + await self.signal_operation_done() + + async def handle_stop_and_clear(self, data: dict): + """Handle a request from the frontend to stop and clear the current TTS queue.""" + + tts_agent: "TTSAgent" = get_agent("tts") + + if not tts_agent: + await self.signal_operation_failed("TTS agent not available") + return + + try: + await tts_agent.stop_and_clear_queue() + await self.signal_operation_done() + except Exception as e: + log.error("Failed to stop and clear TTS queue", error=e) + await self.signal_operation_failed(str(e)) + + async def handle_upload_voice_file(self, data: dict): + """Handle uploading a new audio file for a voice. + + The *provider* defines which MIME types it accepts via + ``VoiceProvider.upload_file_types``. This method therefore: + + 1. Parses the data-URL to obtain the raw bytes **and** MIME type. + 2. Verifies the MIME type against the provider's allowed list + (if the provider restricts uploads). + 3. Stores the file under + + ``tts/voice//.`` + + where *extension* is derived from the MIME type (e.g. ``audio/wav`` → ``wav``). + 4. Returns the relative path ("provider_id") back to the frontend so + it can populate the voice's ``provider_id`` field. + """ + + try: + payload = UploadVoiceFilePayload(**data) + except pydantic.ValidationError as e: + await self.signal_operation_failed(str(e)) + return + + # Check provider allows file uploads + from .providers import provider as get_provider + + P = get_provider(payload.provider) + if not P.allow_file_upload: + await self.signal_operation_failed( + f"Provider '{payload.provider}' does not support file uploads" + ) + return + + # Build filename from label + def slugify(text: str) -> str: + text = text.lower().strip() + text = re.sub(r"[^a-z0-9]+", "-", text) + return text.strip("-") + + filename_no_ext = slugify(payload.label or "voice") or "voice" + + # Determine media type and validate against provider + try: + header, b64data = payload.content.split(",", 1) + media_type = header.split(":", 1)[1].split(";", 1)[0] + except Exception: + await self.signal_operation_failed("Invalid data URL format") + return + + if P.upload_file_types and media_type not in P.upload_file_types: + await self.signal_operation_failed( + f"File type '{media_type}' not allowed for provider '{payload.provider}'" + ) + return + + extension = media_type.split("/")[1] + filename = f"{filename_no_ext}.{extension}" + + # Determine target directory and path + if not payload.as_scene_asset: + target_dir = P.default_voice_dir + else: + target_dir = Path(self.scene.assets.asset_directory) / "tts" + + os.makedirs(target_dir, exist_ok=True) + target_path = target_dir / filename + + log.debug( + "Target path", + target_path=target_path, + as_scene_asset=payload.as_scene_asset, + ) + + # Decode base64 data URL + try: + file_bytes = base64.b64decode(b64data) + except Exception as e: + await self.signal_operation_failed(f"Invalid base64 data: {e}") + return + + try: + with open(target_path, "wb") as f: + f.write(file_bytes) + except Exception as e: + await self.signal_operation_failed(f"Failed to save file: {e}") + return + + provider_id = str(target_path.relative_to(TALEMATE_ROOT)) + + # Send response back to frontend so it can set provider_id + self.websocket_handler.queue_put( + { + "type": self.router, + "action": "voice_file_uploaded", + "provider_id": provider_id, + } + ) + await self.signal_operation_done(signal_only=True) diff --git a/src/talemate/agents/visual/__init__.py b/src/talemate/agents/visual/__init__.py index 30c5f07d..a1a1e019 100644 --- a/src/talemate/agents/visual/__init__.py +++ b/src/talemate/agents/visual/__init__.py @@ -14,10 +14,9 @@ from talemate.agents.base import ( ) from talemate.agents.registry import register from talemate.agents.editor.revision import RevisionDisabled +from talemate.agents.summarize.analyze_scene import SceneAnalysisDisabled from talemate.client.base import ClientBase -from talemate.config import load_config from talemate.emit import emit -from talemate.emit.signals import handlers as signal_handlers from talemate.prompts.base import Prompt from .commands import * # noqa @@ -152,16 +151,13 @@ class VisualBase(Agent): return actions - def __init__(self, client: ClientBase, *kwargs): + def __init__(self, client: ClientBase | None = None, **kwargs): self.client = client self.is_enabled = False self.backend_ready = False self.initialized = False - self.config = load_config() self.actions = VisualBase.init_actions() - signal_handlers["config_saved"].connect(self.on_config_saved) - @property def enabled(self): return self.is_enabled @@ -231,6 +227,10 @@ class VisualBase(Agent): or f"{self.backend_name} is not ready for processing", ).model_dump() + backend_detail_fn = getattr(self, f"{self.backend.lower()}_agent_details", None) + if backend_detail_fn: + details.update(backend_detail_fn()) + return details @property @@ -241,11 +241,6 @@ class VisualBase(Agent): def allow_automatic_generation(self): return self.actions["automatic_generation"].enabled - def on_config_saved(self, event): - config = event.data - self.config = config - asyncio.create_task(self.emit_status()) - async def on_ready_check_success(self): prev_ready = self.backend_ready self.backend_ready = True @@ -406,7 +401,11 @@ class VisualBase(Agent): f"data:image/png;base64,{image}" ) character.cover_image = asset.id - self.scene.assets.cover_image = asset.id + + # Only set scene cover image if scene doesn't already have one + if not self.scene.assets.cover_image: + self.scene.assets.cover_image = asset.id + self.scene.emit_status() async def emit_image(self, image: str): @@ -538,7 +537,7 @@ class VisualBase(Agent): @set_processing async def generate_environment_prompt(self, instructions: str = None): - with RevisionDisabled(): + with RevisionDisabled(), SceneAnalysisDisabled(): response = await Prompt.request( "visual.generate-environment-prompt", self.client, @@ -557,7 +556,7 @@ class VisualBase(Agent): ): character = self.scene.get_character(character_name) - with RevisionDisabled(): + with RevisionDisabled(), SceneAnalysisDisabled(): response = await Prompt.request( "visual.generate-character-prompt", self.client, diff --git a/src/talemate/agents/visual/comfyui.py b/src/talemate/agents/visual/comfyui.py index 63be0185..ac6d71c2 100644 --- a/src/talemate/agents/visual/comfyui.py +++ b/src/talemate/agents/visual/comfyui.py @@ -10,7 +10,12 @@ import httpx import pydantic import structlog -from talemate.agents.base import AgentAction, AgentActionConditional, AgentActionConfig +from talemate.agents.base import ( + AgentAction, + AgentActionConditional, + AgentActionConfig, + AgentDetail, +) from .handlers import register from .schema import RenderSettings, Resolution @@ -164,11 +169,16 @@ class ComfyUIMixin: label="Checkpoint", choices=[], description="The main checkpoint to use.", + note="If the agent is enabled and connected, but the checkpoint list is empty, try closing this window and opening it again.", ), }, ) } + @property + def comfyui_checkpoint(self): + return self.actions["comfyui"].config["checkpoint"].value + @property def comfyui_workflow_filename(self): base_name = self.actions["comfyui"].config["workflow"].value @@ -219,10 +229,27 @@ class ComfyUIMixin: async def comfyui_checkpoints(self): loader_node = (await self.comfyui_object_info)["CheckpointLoaderSimple"] _checkpoints = loader_node["input"]["required"]["ckpt_name"][0] + log.debug("comfyui_checkpoints", _checkpoints=_checkpoints) return [ {"label": checkpoint, "value": checkpoint} for checkpoint in _checkpoints ] + def comfyui_agent_details(self): + checkpoint: str = self.comfyui_checkpoint + if not checkpoint: + return {} + + # remove .safetensors + checkpoint = checkpoint.replace(".safetensors", "") + + return { + "checkpoint": AgentDetail( + icon="mdi-brain", + value=checkpoint, + description="The checkpoint to use for comfyui", + ).model_dump() + } + async def comfyui_get_image(self, filename: str, subfolder: str, folder_type: str): data = {"filename": filename, "subfolder": subfolder, "type": folder_type} url_values = urllib.parse.urlencode(data) diff --git a/src/talemate/agents/visual/openai_image.py b/src/talemate/agents/visual/openai_image.py index c09bfc0d..40e2955b 100644 --- a/src/talemate/agents/visual/openai_image.py +++ b/src/talemate/agents/visual/openai_image.py @@ -55,7 +55,7 @@ class OpenAIImageMixin: @property def openai_api_key(self): - return self.config.get("openai", {}).get("api_key") + return self.config.openai.api_key @property def openai_model_type(self): diff --git a/src/talemate/agents/visual/websocket_handler.py b/src/talemate/agents/visual/websocket_handler.py index 8694ffdf..fd0a66e6 100644 --- a/src/talemate/agents/visual/websocket_handler.py +++ b/src/talemate/agents/visual/websocket_handler.py @@ -91,7 +91,7 @@ class VisualWebsocketHandler(Plugin): await visual.generate_character_portrait( payload.context.character_name, payload.context.instructions, - replace=True, + replace=payload.context.replace, prompt_only=payload.context.prompt_only, ) diff --git a/src/talemate/agents/world_state/__init__.py b/src/talemate/agents/world_state/__init__.py index acb0f8da..84129d0b 100644 --- a/src/talemate/agents/world_state/__init__.py +++ b/src/talemate/agents/world_state/__init__.py @@ -13,6 +13,7 @@ import talemate.util as util from talemate.emit import emit from talemate.events import GameLoopEvent from talemate.instance import get_agent +from talemate.client import ClientBase from talemate.prompts import Prompt from talemate.scene_message import ( ReinforcementMessage, @@ -125,7 +126,7 @@ class WorldStateAgent(CharacterProgressionMixin, Agent): CharacterProgressionMixin.add_actions(actions) return actions - def __init__(self, client, **kwargs): + def __init__(self, client: ClientBase | None = None, **kwargs): self.client = client self.is_enabled = True self.next_update = 0 diff --git a/src/talemate/character.py b/src/talemate/character.py index 9091e80b..b7e4eb0b 100644 --- a/src/talemate/character.py +++ b/src/talemate/character.py @@ -1,16 +1,539 @@ from typing import TYPE_CHECKING, Union +import pydantic +import structlog +import random +import re +import traceback -from talemate.instance import get_agent +import talemate.util as util +import talemate.instance as instance +import talemate.scene_message as scene_message +import talemate.agents.base as agent_base +from talemate.agents.tts.schema import Voice +import talemate.emit.async_signals as async_signals if TYPE_CHECKING: - from talemate.tale_mate import Character, Scene - + from talemate.tale_mate import Scene, Actor __all__ = [ + "Character", + "VoiceChangedEvent", "deactivate_character", "activate_character", + "set_voice", ] +log = structlog.get_logger("talemate.character") + +async_signals.register("character.voice_changed") + + +class Character(pydantic.BaseModel): + # core character information + name: str + description: str = "" + greeting_text: str = "" + color: str = "#fff" + is_player: bool = False + memory_dirty: bool = False + cover_image: str | None = None + voice: Voice | None = None + + # dialogue instructions and examples + dialogue_instructions: str | None = None + example_dialogue: list[str] = pydantic.Field(default_factory=list) + + # attribute and detail storage + base_attributes: dict[str, str | int | float | bool] = pydantic.Field( + default_factory=dict + ) + details: dict[str, str] = pydantic.Field(default_factory=dict) + + # helpful references + agent: agent_base.Agent | None = pydantic.Field(default=None, exclude=True) + actor: "Actor | None" = pydantic.Field(default=None, exclude=True) + + class Config: + arbitrary_types_allowed = True + + @property + def gender(self) -> str: + return self.base_attributes.get("gender", "") + + @property + def sheet(self) -> str: + sheet = self.base_attributes or { + "name": self.name, + "description": self.description, + } + + sheet_list = [] + + for key, value in sheet.items(): + sheet_list.append(f"{key}: {value}") + + return "\n".join(sheet_list) + + @property + def random_dialogue_example(self): + """ + Get a random example dialogue line for this character. + + Returns: + str: The random example dialogue line. + """ + if not self.example_dialogue: + return "" + + return random.choice(self.example_dialogue) + + def __str__(self): + return f"Character: {self.name}" + + def __repr__(self): + return str(self) + + def __hash__(self): + return hash(self.name) + + def set_color(self, color: str | None = None): + # if no color provided, chose a random color + + if color is None: + color = util.random_color() + self.color = color + + def set_cover_image(self, asset_id: str, initial_only: bool = False): + if self.cover_image and initial_only: + return + + self.cover_image = asset_id + + def sheet_filtered(self, *exclude): + sheet = self.base_attributes or { + "name": self.name, + "gender": self.gender, + "description": self.description, + } + + sheet_list = [] + + for key, value in sheet.items(): + if key not in exclude: + sheet_list.append(f"{key}: {value}") + + return "\n".join(sheet_list) + + def random_dialogue_examples( + self, + scene: "Scene", + num: int = 3, + strip_name: bool = False, + max_backlog: int = 250, + max_length: int = 192, + history_threshold: int = 15, + ) -> list[str]: + """ + Get multiple random example dialogue lines for this character. + + Will return up to `num` examples and not have any duplicates. + """ + + if len(scene.history) < history_threshold and self.example_dialogue: + # when history is too short, we just use from the prepared + # examples + return self._random_dialogue_examples(num, strip_name) + + history_examples = self._random_dialogue_examples_from_history( + scene, num, max_backlog + ) + + if len(history_examples) < num: + random_examples = self._random_dialogue_examples( + num - len(history_examples), strip_name + ) + + for example in random_examples: + history_examples.append(example) + + # ensure sane example lengths + + history_examples = [ + util.strip_partial_sentences(example[:max_length]) + for example in history_examples + ] + + log.debug("random_dialogue_examples", history_examples=history_examples) + return history_examples + + def _random_dialogue_examples_from_history( + self, scene: "Scene", num: int = 3, max_backlog: int = 250 + ) -> list[str]: + """ + Get multiple random example dialogue lines for this character from the scene's history. + + Will checks the last `max_backlog` messages in the scene's history and returns up to `num` examples. + """ + + history = scene.history[-max_backlog:] + + examples = [] + + for message in history: + if not isinstance(message, scene_message.CharacterMessage): + continue + + if message.character_name != self.name: + continue + + examples.append(message.without_name.strip()) + + if not examples: + return [] + + return random.sample(examples, min(num, len(examples))) + + def _random_dialogue_examples( + self, num: int = 3, strip_name: bool = False + ) -> list[str]: + """ + Get multiple random example dialogue lines for this character. + + Will return up to `num` examples and not have any duplicates. + """ + + if not self.example_dialogue: + return [] + + # create copy of example_dialogue so we dont modify the original + + examples = self.example_dialogue.copy() + + # shuffle the examples so we get a random order + + random.shuffle(examples) + + # now pop examples until we have `num` examples or we run out of examples + + if strip_name: + examples = [example.split(":", 1)[1].strip() for example in examples] + + return [examples.pop() for _ in range(min(num, len(examples)))] + + def filtered_sheet(self, attributes: list[str]): + """ + Same as sheet but only returns the attributes in the given list + + Attributes that dont exist will be ignored + """ + + sheet_list = [] + + for key, value in self.base_attributes.items(): + if key.lower() not in attributes: + continue + sheet_list.append(f"{key}: {value}") + + return "\n".join(sheet_list) + + def rename(self, new_name: str): + """ + Rename the character. + + Args: + new_name (str): The new name of the character. + + Returns: + None + """ + + orig_name = self.name + self.name = new_name + + if orig_name.lower() == "you": + # we dont want to replace "you" in the description + # or anywhere else so we can just return here + return + + if self.description: + self.description = self.description.replace(f"{orig_name}", self.name) + for k, v in self.base_attributes.items(): + if isinstance(v, str): + self.base_attributes[k] = v.replace(f"{orig_name}", self.name) + for i, v in list(self.details.items()): + if isinstance(v, str): + self.details[i] = v.replace(f"{orig_name}", self.name) + self.memory_dirty = True + + def introduce_main_character(self, character: "Character"): + """ + Makes this character aware of the main character's name in the scene. + + This will replace all occurrences of {{user}} (case-insensitive) in all of the character's properties + with the main character's name. + """ + + properties = ["description", "greeting_text"] + + pattern = re.compile(re.escape("{{user}}"), re.IGNORECASE) + + for prop in properties: + prop_value = getattr(self, prop) + + try: + updated_prop_value = pattern.sub(character.name, prop_value) + except Exception as e: + log.error( + "introduce_main_character", + error=e, + traceback=traceback.format_exc(), + ) + updated_prop_value = prop_value + setattr(self, prop, updated_prop_value) + + # also replace in all example dialogue + + for i, dialogue in enumerate(self.example_dialogue): + self.example_dialogue[i] = pattern.sub(character.name, dialogue) + + def update(self, **kwargs): + """ + Update character properties with given key-value pairs. + """ + + for key, value in kwargs.items(): + setattr(self, key, value) + + self.memory_dirty = True + + async def purge_from_memory(self): + """ + Purges this character's details from memory. + """ + memory_agent = instance.get_agent("memory") + await memory_agent.delete({"character": self.name}) + log.info("purged character from memory", character=self.name) + + async def commit_to_memory(self, memory_agent): + """ + Commits this character's details to the memory agent. (vectordb) + """ + + items = [] + + if not self.base_attributes or "description" not in self.base_attributes: + if not self.description: + self.description = "" + description_chunks = [ + chunk.strip() for chunk in self.description.split("\n") if chunk.strip() + ] + + for idx in range(len(description_chunks)): + chunk = description_chunks[idx] + + items.append( + { + "text": f"{self.name}: {chunk}", + "id": f"{self.name}.description.{idx}", + "meta": { + "character": self.name, + "attr": "description", + "typ": "base_attribute", + }, + } + ) + + seen_attributes = set() + + for attr, value in self.base_attributes.items(): + if attr.startswith("_"): + continue + + if attr.lower() in ["name", "scenario_context", "_prompt", "_template"]: + continue + + seen_attributes.add(attr) + + items.append( + { + "text": f"{self.name}'s {attr}: {value}", + "id": f"{self.name}.{attr}", + "meta": { + "character": self.name, + "attr": attr, + "typ": "base_attribute", + }, + } + ) + + for key, detail in self.details.items(): + # if colliding with attribute name, prefix with detail_ + if key in seen_attributes: + key = f"detail_{key}" + + items.append( + { + "text": f"{self.name} - {key}: {detail}", + "id": f"{self.name}.{key}", + "meta": { + "character": self.name, + "typ": "details", + "detail": key, + }, + } + ) + + if items: + await memory_agent.add_many(items) + + self.memory_dirty = False + + async def commit_single_attribute_to_memory( + self, memory_agent, attribute: str, value: str + ): + """ + Commits a single attribute to memory + """ + + items = [] + + # remove old attribute if it exists + + await memory_agent.delete( + {"character": self.name, "typ": "base_attribute", "attr": attribute} + ) + + self.base_attributes[attribute] = value + + items.append( + { + "text": f"{self.name}'s {attribute}: {self.base_attributes[attribute]}", + "id": f"{self.name}.{attribute}", + "meta": { + "character": self.name, + "attr": attribute, + "typ": "base_attribute", + }, + } + ) + + log.debug("commit_single_attribute_to_memory", items=items) + + await memory_agent.add_many(items) + + async def commit_single_detail_to_memory( + self, memory_agent, detail: str, value: str + ): + """ + Commits a single detail to memory + """ + + items = [] + + # remove old detail if it exists + + await memory_agent.delete( + {"character": self.name, "typ": "details", "detail": detail} + ) + + self.details[detail] = value + + items.append( + { + "text": f"{self.name} - {detail}: {value}", + "id": f"{self.name}.{detail}", + "meta": { + "character": self.name, + "typ": "details", + "detail": detail, + }, + } + ) + + log.debug("commit_single_detail_to_memory", items=items) + + await memory_agent.add_many(items) + + async def set_detail(self, name: str, value): + memory_agent = instance.get_agent("memory") + if not value: + try: + del self.details[name] + await memory_agent.delete( + {"character": self.name, "typ": "details", "detail": name} + ) + except KeyError: + pass + else: + self.details[name] = value + await self.commit_single_detail_to_memory(memory_agent, name, value) + + def set_detail_defer(self, name: str, value): + self.details[name] = value + self.memory_dirty = True + + def get_detail(self, name: str): + return self.details.get(name) + + async def set_base_attribute(self, name: str, value): + memory_agent = instance.get_agent("memory") + + if not value: + try: + del self.base_attributes[name] + await memory_agent.delete( + {"character": self.name, "typ": "base_attribute", "attr": name} + ) + except KeyError: + pass + else: + self.base_attributes[name] = value + await self.commit_single_attribute_to_memory(memory_agent, name, value) + + def set_base_attribute_defer(self, name: str, value): + self.base_attributes[name] = value + self.memory_dirty = True + + def get_base_attribute(self, name: str): + return self.base_attributes.get(name) + + async def set_description(self, description: str): + memory_agent = instance.get_agent("memory") + self.description = description + + items = [] + + await memory_agent.delete( + {"character": self.name, "typ": "base_attribute", "attr": "description"} + ) + + description_chunks = [ + chunk.strip() for chunk in self.description.split("\n") if chunk.strip() + ] + + for idx in range(len(description_chunks)): + chunk = description_chunks[idx] + + items.append( + { + "text": f"{self.name}: {chunk}", + "id": f"{self.name}.description.{idx}", + "meta": { + "character": self.name, + "attr": "description", + "typ": "base_attribute", + }, + } + ) + + await memory_agent.add_many(items) + + +class VoiceChangedEvent(pydantic.BaseModel): + character: "Character" + voice: Voice | None + auto: bool = False + async def deactivate_character(scene: "Scene", character: Union[str, "Character"]): """ @@ -51,9 +574,18 @@ async def activate_character(scene: "Scene", character: Union[str, "Character"]) return False if not character.is_player: - actor = scene.Actor(character, get_agent("conversation")) + actor = scene.Actor(character, instance.get_agent("conversation")) else: actor = scene.Player(character, None) await scene.add_actor(actor) del scene.inactive_characters[character.name] + + +async def set_voice(character: "Character", voice: Voice | None, auto: bool = False): + character.voice = voice + emission: VoiceChangedEvent = VoiceChangedEvent( + character=character, voice=voice, auto=auto + ) + await async_signals.get("character.voice_changed").send(emission) + return emission diff --git a/src/talemate/client/anthropic.py b/src/talemate/client/anthropic.py index 6b6546c8..860f16d0 100644 --- a/src/talemate/client/anthropic.py +++ b/src/talemate/client/anthropic.py @@ -9,10 +9,8 @@ from talemate.client.remote import ( EndpointOverrideMixin, endpoint_override_extra_fields, ) -from talemate.config import Client as BaseClientConfig -from talemate.config import load_config +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit -from talemate.emit.signals import handlers __all__ = [ "AnthropicClient", @@ -33,10 +31,13 @@ SUPPORTED_MODELS = [ "claude-opus-4-20250514", ] +DEFAULT_MODEL = "claude-3-5-sonnet-latest" +MIN_THINKING_TOKENS = 1024 + class Defaults(EndpointOverride, CommonDefaults, pydantic.BaseModel): max_token_length: int = 16384 - model: str = "claude-3-5-sonnet-latest" + model: str = DEFAULT_MODEL double_coercion: str = None @@ -52,7 +53,6 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): client_type = "anthropic" conversation_retries = 0 - auto_break_repetition_enabled = False # TODO: make this configurable? decensor_enabled = False config_cls = ClientConfig @@ -66,22 +66,13 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): defaults: Defaults = Defaults() extra_fields: dict[str, ExtraField] = endpoint_override_extra_fields() - def __init__(self, model="claude-3-5-sonnet-latest", **kwargs): - self.model_name = model - self.api_key_status = None - self._reconfigure_endpoint_override(**kwargs) - self.config = load_config() - super().__init__(**kwargs) - - handlers["config_saved"].connect(self.on_config_saved) - @property def can_be_coerced(self) -> bool: - return True + return not self.reason_enabled @property def anthropic_api_key(self): - return self.config.get("anthropic", {}).get("api_key") + return self.config.anthropic.api_key @property def supported_parameters(self): @@ -92,17 +83,25 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): "max_tokens", ] + @property + def min_reason_tokens(self) -> int: + return MIN_THINKING_TOKENS + + @property + def requires_reasoning_pattern(self) -> bool: + return False + def emit_status(self, processing: bool = None): error_action = None + error_message: str | None = None if processing is not None: self.processing = processing if self.anthropic_api_key: status = "busy" if self.processing else "idle" - model_name = self.model_name else: status = "error" - model_name = "No API key set" + error_message = "No API key set" error_action = ErrorAction( title="Set API Key", action_name="openAppConfig", @@ -115,7 +114,7 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): if not self.model_name: status = "error" - model_name = "No model loaded" + error_message = "No model loaded" self.current_status = status @@ -124,73 +123,18 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): "double_coercion": self.double_coercion, "meta": self.Meta().model_dump(), "enabled": self.enabled, + "error_message": error_message, } data.update(self._common_status_data()) emit( "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status if self.enabled else "disabled", data=data, ) - def set_client(self, max_token_length: int = None): - if ( - not self.anthropic_api_key - and not self.endpoint_override_base_url_configured - ): - self.client = AsyncAnthropic(api_key="sk-1111") - log.error("No anthropic API key set") - if self.api_key_status: - self.api_key_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = "claude-3-opus-20240229" - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - - model = self.model_name - - self.client = AsyncAnthropic(api_key=self.api_key, base_url=self.base_url) - self.max_token_length = max_token_length or 16384 - - if not self.api_key_status: - if self.api_key_status is False: - emit("request_client_status") - emit("request_agent_status") - self.api_key_status = True - - log.info( - "anthropic set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=model, - ) - - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - if "double_coercion" in kwargs: - self.double_coercion = kwargs["double_coercion"] - - self._reconfigure_common_parameters(**kwargs) - self._reconfigure_endpoint_override(**kwargs) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - def response_tokens(self, response: str): return response.usage.output_tokens @@ -200,13 +144,6 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): async def status(self): self.emit_status() - def prompt_template(self, system_message: str, prompt: str): - """ - Anthropic handles the prompt template internally, so we just - give the prompt as is. - """ - return prompt - async def generate(self, prompt: str, parameters: dict, kind: str): """ Generates text from the given prompt and parameters. @@ -218,17 +155,35 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): ): raise Exception("No anthropic API key set") - prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + client = AsyncAnthropic(api_key=self.api_key, base_url=self.base_url) + + if self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + else: + coercion_prompt = None system_message = self.get_system_message(kind) messages = [{"role": "user", "content": prompt.strip()}] if coercion_prompt: + log.debug("Adding coercion pre-fill", coercion_prompt=coercion_prompt) messages.append({"role": "assistant", "content": coercion_prompt.strip()}) + if self.reason_enabled: + parameters["thinking"] = { + "type": "enabled", + "budget_tokens": self.validated_reason_tokens, + } + # thinking doesn't support temperature, top_p, or top_k + # and the API will error if they are set + parameters.pop("temperature", None) + parameters.pop("top_p", None) + parameters.pop("top_k", None) + self.log.debug( "generate", + model=self.model_name, prompt=prompt[:128] + " ...", parameters=parameters, system_message=system_message, @@ -238,7 +193,7 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): prompt_tokens = 0 try: - stream = await self.client.messages.create( + stream = await client.messages.create( model=self.model_name, system=system_message, messages=messages, @@ -247,13 +202,25 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): ) response = "" + reasoning = "" async for event in stream: - if event.type == "content_block_delta": + if ( + event.type == "content_block_delta" + and event.delta.type == "text_delta" + ): content = event.delta.text response += content self.update_request_tokens(self.count_tokens(content)) + elif ( + event.type == "content_block_delta" + and event.delta.type == "thinking_delta" + ): + content = event.delta.thinking + reasoning += content + self.update_request_tokens(self.count_tokens(content)) + elif event.type == "message_start": prompt_tokens = event.message.usage.input_tokens @@ -262,8 +229,9 @@ class AnthropicClient(EndpointOverrideMixin, ClientBase): self._returned_prompt_tokens = prompt_tokens self._returned_response_tokens = completion_tokens + self._reasoning_response = reasoning - log.debug("generated response", response=response) + log.debug("generated response", response=response, reasoning=reasoning) return response except PermissionDeniedError as e: diff --git a/src/talemate/client/base.py b/src/talemate/client/base.py index ff1f1afb..6e8c9605 100644 --- a/src/talemate/client/base.py +++ b/src/talemate/client/base.py @@ -4,6 +4,7 @@ A unified client base, based on the openai API import ipaddress import logging +import re import random import time import traceback @@ -14,20 +15,27 @@ import pydantic import dataclasses import structlog import urllib3 -from openai import AsyncOpenAI, PermissionDeniedError +from openai import PermissionDeniedError import talemate.client.presets as presets import talemate.instance as instance import talemate.util as util from talemate.agents.context import active_agent from talemate.client.context import client_context_attribute -from talemate.client.model_prompts import model_prompt +from talemate.client.model_prompts import model_prompt, DEFAULT_TEMPLATE from talemate.client.ratelimit import CounterRateLimiter from talemate.context import active_scene +from talemate.prompts.base import Prompt from talemate.emit import emit -from talemate.config import load_config, save_config, EmbeddingFunctionPreset +from talemate.config import get_config, Config +from talemate.config.schema import EmbeddingFunctionPreset, Client as ClientConfig import talemate.emit.async_signals as async_signals -from talemate.exceptions import SceneInactiveError, GenerationCancelled +from talemate.exceptions import ( + SceneInactiveError, + GenerationCancelled, + GenerationProcessingError, + ReasoningResponseError, +) import talemate.ux.schema as ux_schema from talemate.client.system_prompts import SystemPrompts @@ -43,6 +51,11 @@ STOPPING_STRINGS = ["<|im_end|>", ""] REPLACE_SMART_QUOTES = True +INDIRECT_COERCION_PROMPT = "\nStart your response with: " + +DEFAULT_REASONING_PATTERN = r".*?" + + class ClientDisabledError(OSError): def __init__(self, client: "ClientBase"): self.client = client @@ -63,6 +76,7 @@ class PromptData(pydantic.BaseModel): generation_parameters: dict = pydantic.Field(default_factory=dict) inference_preset: str = None preset_group: str | None = None + reasoning: str | None = None class ErrorAction(pydantic.BaseModel): @@ -76,6 +90,9 @@ class CommonDefaults(pydantic.BaseModel): rate_limit: int | None = None data_format: Literal["yaml", "json"] | None = None preset_group: str | None = None + reason_enabled: bool = False + reason_tokens: int = 0 + reason_response_pattern: str | None = None class Defaults(CommonDefaults, pydantic.BaseModel): @@ -99,6 +116,7 @@ class ExtraField(pydantic.BaseModel): description: str group: FieldGroup | None = None note: ux_schema.Note | None = None + choices: list[str | int | float | bool] | None = None class ParameterReroute(pydantic.BaseModel): @@ -162,39 +180,36 @@ class RequestInformation(pydantic.BaseModel): class ClientEmbeddingsStatus: client: "ClientBase | None" = None embedding_name: str | None = None + seen: bool = False + + +@dataclasses.dataclass +class ClientStatus: + client: "ClientBase | None" = None + enabled: bool = False async_signals.register( "client.embeddings_available", + "client.enabled", + "client.disabled", ) class ClientBase: - api_url: str - model_name: str - api_key: str = None - name: str = None - enabled: bool = True + name: str + remote_model_name: str | None = None + remote_model_locked: bool = False current_status: str = None - max_token_length: int = 8192 processing: bool = False connected: bool = False conversation_retries: int = 0 - auto_break_repetition_enabled: bool = True decensor_enabled: bool = True auto_determine_prompt_template: bool = False finalizers: list[str] = [] - double_coercion: Union[str, None] = None - data_format: Literal["yaml", "json"] | None = None - rate_limit: int | None = None client_type = "base" request_information: RequestInformation | None = None - status_request_timeout: int = 2 - - system_prompts: SystemPrompts = SystemPrompts() - preset_group: str | None = "" - rate_limit_counter: CounterRateLimiter = None class Meta(pydantic.BaseModel): @@ -207,27 +222,92 @@ class ClientBase: def __init__( self, - api_url: str = None, name: str = None, **kwargs, ): - self.api_url = api_url self.name = name or self.client_type + self.remote_model_name = None self.auto_determine_prompt_template_attempt = None self.log = structlog.get_logger(f"client.{self.client_type}") - self.double_coercion = kwargs.get("double_coercion", None) - self._reconfigure_common_parameters(**kwargs) - self.enabled = kwargs.get("enabled", True) - if "max_token_length" in kwargs: - self.max_token_length = ( - int(kwargs["max_token_length"]) if kwargs["max_token_length"] else 8192 - ) - - self.set_client(max_token_length=self.max_token_length) def __str__(self): return f"{self.client_type}Client[{self.api_url}][{self.model_name or ''}]" + ##### + + # config getters + + @property + def config(self) -> Config: + return get_config() + + @property + def client_config(self) -> ClientConfig: + try: + return get_config().clients[self.name] + except KeyError: + return ClientConfig(type=self.client_type, name=self.name) + + @property + def model(self) -> str | None: + return self.client_config.model + + @property + def model_name(self) -> str | None: + if self.remote_model_locked: + return self.remote_model_name + return self.remote_model_name or self.model + + @property + def api_key(self) -> str | None: + return self.client_config.api_key + + @property + def api_url(self) -> str | None: + return self.client_config.api_url + + @property + def max_token_length(self) -> int: + return self.client_config.max_token_length + + @property + def double_coercion(self) -> str | None: + return self.client_config.double_coercion + + @property + def rate_limit(self) -> int | None: + return self.client_config.rate_limit + + @property + def data_format(self) -> Literal["yaml", "json"]: + return self.client_config.data_format + + @property + def enabled(self) -> bool: + return self.client_config.enabled + + @property + def system_prompts(self) -> SystemPrompts: + return self.client_config.system_prompts + + @property + def preset_group(self) -> str | None: + return self.client_config.preset_group + + @property + def reason_enabled(self) -> bool: + return self.client_config.reason_enabled + + @property + def reason_tokens(self) -> int: + return self.client_config.reason_tokens + + @property + def reason_response_pattern(self) -> str: + return self.client_config.reason_response_pattern or DEFAULT_REASONING_PATTERN + + ##### + @property def experimental(self): return False @@ -238,6 +318,9 @@ class ClientBase: Determines whether or not his client can pass LLM coercion. (e.g., is able to predefine partial LLM output in the prompt) """ + if self.reason_enabled: + # We are not able to coerce via pre-filling if reasoning is enabled + return False return self.Meta().requires_prompt_template @property @@ -283,7 +366,49 @@ class ClientBase: def embeddings_identifier(self) -> str: return f"client-api/{self.name}/{self.embeddings_model_name}" - async def destroy(self, config: dict): + @property + def reasoning_response(self) -> str | None: + return getattr(self, "_reasoning_response", None) + + @property + def min_reason_tokens(self) -> int: + return 0 + + @property + def validated_reason_tokens(self) -> int: + return max(self.reason_tokens, self.min_reason_tokens) + + @property + def default_prompt_template(self) -> str: + return DEFAULT_TEMPLATE + + @property + def requires_reasoning_pattern(self) -> bool: + return True + + async def enable(self): + self.client_config.enabled = True + self.emit_status() + + await self.config.set_dirty() + await self.status() + await async_signals.get("client.enabled").send( + ClientStatus(client=self, enabled=True) + ) + + async def disable(self): + self.client_config.enabled = False + self.emit_status() + + if self.supports_embeddings: + await self.reset_embeddings() + await self.config.set_dirty() + await self.status() + await async_signals.get("client.disabled").send( + ClientStatus(client=self, enabled=False) + ) + + async def destroy(self): """ This is called before the client is removed from talemate.instance.clients @@ -294,16 +419,13 @@ class ClientBase: """ if self.supports_embeddings: - self.remove_embeddings(config) + await self.remove_embeddings() - def reset_embeddings(self): + async def reset_embeddings(self): self._embeddings_model_name = None self._embeddings_status = False - def set_client(self, **kwargs): - self.client = AsyncOpenAI(base_url=self.api_url, api_key="sk-1111") - - def set_embeddings(self): + async def set_embeddings(self): log.debug( "setting embeddings", client=self.name, @@ -314,7 +436,7 @@ class ClientBase: if not self.supports_embeddings or not self.embeddings_status: return - config = load_config(as_model=True) + config: Config = get_config() key = self.embeddings_identifier @@ -334,30 +456,25 @@ class ClientBase: custom=True, ) - save_config(config) + await config.set_dirty() - def remove_embeddings(self, config: dict | None = None): + async def remove_embeddings(self): # remove all embeddings for this client - for key, value in list(config["presets"]["embeddings"].items()): - if value["client"] == self.name and value["embeddings"] == "client-api": + config: Config = get_config() + for key, value in list(config.presets.embeddings.items()): + if value.client == self.name and value.embeddings == "client-api": log.warning("!!! removing embeddings", client=self.name, key=key) - config["presets"]["embeddings"].pop(key) - - def set_system_prompts(self, system_prompts: dict | SystemPrompts): - if isinstance(system_prompts, dict): - self.system_prompts = SystemPrompts(**system_prompts) - elif not isinstance(system_prompts, SystemPrompts): - raise ValueError( - "system_prompts must be a `dict` or `SystemPrompts` instance" - ) - else: - self.system_prompts = system_prompts + config.presets.embeddings.pop(key) + await config.set_dirty() def prompt_template(self, sys_msg: str, prompt: str): """ Applies the appropriate prompt template for the model. """ + if not self.Meta().requires_prompt_template: + return prompt + if not self.model_name: self.log.warning("prompt template not applied", reason="no model loaded") return f"{sys_msg}\n{prompt}" @@ -372,13 +489,22 @@ class ClientBase: else: double_coercion = None - return model_prompt(self.model_name, sys_msg, prompt, double_coercion)[0] + return model_prompt( + self.model_name, + sys_msg, + prompt, + double_coercion, + default_template=self.default_prompt_template, + )[0] def prompt_template_example(self): if not getattr(self, "model_name", None): return None, None return model_prompt( - self.model_name, "{sysmsg}", "{prompt}<|BOT|>{LLM coercion}" + self.model_name, + "{sysmsg}", + "{prompt}<|BOT|>{LLM coercion}", + default_template=self.default_prompt_template, ) def split_prompt_for_coercion(self, prompt: str) -> tuple[str, str]: @@ -386,59 +512,29 @@ class ClientBase: Splits the prompt and the prefill/coercion prompt. """ if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) + prompt, coercion = prompt.split("<|BOT|>", 1) if self.double_coercion: - right = f"{self.double_coercion}\n\n{right}" + coercion = f"{self.double_coercion}\n\n{coercion}" - return prompt, right + return prompt, coercion return prompt, None - def reconfigure(self, **kwargs): + def rate_limit_update(self): """ - Reconfigures the client. + Updates the rate limit counter for the client. - Keyword Arguments: - - - api_url: the API URL to use - - max_token_length: the max token length to use - - enabled: whether the client is enabled + If the rate limit is set to 0, the rate limit counter is set to None. """ - - if "api_url" in kwargs: - self.api_url = kwargs["api_url"] - - if kwargs.get("max_token_length"): - self.max_token_length = int(kwargs["max_token_length"]) - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - if not self.enabled and self.supports_embeddings and self.embeddings_status: - self.reset_embeddings() - - if "double_coercion" in kwargs: - self.double_coercion = kwargs["double_coercion"] - - self._reconfigure_common_parameters(**kwargs) - - def _reconfigure_common_parameters(self, **kwargs): - if "rate_limit" in kwargs: - self.rate_limit = kwargs["rate_limit"] - if self.rate_limit: - if not self.rate_limit_counter: - self.rate_limit_counter = CounterRateLimiter( - rate_per_minute=self.rate_limit - ) - else: - self.rate_limit_counter.update_rate_limit(self.rate_limit) + if self.rate_limit: + if not self.rate_limit_counter: + self.rate_limit_counter = CounterRateLimiter( + rate_per_minute=self.rate_limit + ) else: - self.rate_limit_counter = None - - if "data_format" in kwargs: - self.data_format = kwargs["data_format"] - - if "preset_group" in kwargs: - self.preset_group = kwargs["preset_group"] + self.rate_limit_counter.update_rate_limit(self.rate_limit) + else: + self.rate_limit_counter = None def host_is_remote(self, url: str) -> bool: """ @@ -491,43 +587,40 @@ class ClientBase: - kind: the kind of generation """ - - app_config_system_prompts = client_context_attribute( - "app_config_system_prompts" - ) - - if app_config_system_prompts: - self.system_prompts.parent = SystemPrompts(**app_config_system_prompts) - - return self.system_prompts.get(kind, self.decensor_enabled) + config: Config = get_config() + self.system_prompts.parent = config.system_prompts + sys_prompt = self.system_prompts.get(kind, self.decensor_enabled) + return sys_prompt def emit_status(self, processing: bool = None): """ Sets and emits the client status. """ + error_message: str | None = None if processing is not None: self.processing = processing if not self.enabled: status = "disabled" - model_name = "Disabled" + error_message = "Disabled" elif not self.connected: status = "error" - model_name = "Could not connect" + error_message = "Could not connect" elif self.model_name: status = "busy" if self.processing else "idle" - model_name = self.model_name else: - model_name = "No model loaded" + error_message = "No model loaded" status = "warning" status_change = status != self.current_status self.current_status = status + default_prompt_template = self.default_prompt_template + prompt_template_example, prompt_template_file = self.prompt_template_example() has_prompt_template = ( - prompt_template_file and prompt_template_file != "default.jinja2" + prompt_template_file and prompt_template_file != default_prompt_template ) if not has_prompt_template and self.auto_determine_prompt_template: @@ -545,21 +638,28 @@ class ClientBase: self.prompt_template_example() ) has_prompt_template = ( - prompt_template_file and prompt_template_file != "default.jinja2" + prompt_template_file + and prompt_template_file != default_prompt_template ) + dedicated_default_template = default_prompt_template != DEFAULT_TEMPLATE + data = { - "api_key": self.api_key, "prompt_template_example": prompt_template_example, "has_prompt_template": has_prompt_template, + "dedicated_default_template": dedicated_default_template, "template_file": prompt_template_file, "meta": self.Meta().model_dump(), "error_action": None, "double_coercion": self.double_coercion, "enabled": self.enabled, "system_prompts": self.system_prompts.model_dump(), + "error_message": error_message, } + if self.Meta().enable_api_auth: + data["api_key"] = self.api_key + data.update(self._common_status_data()) for field_name in getattr(self.Meta(), "extra_fields", {}).keys(): @@ -571,7 +671,7 @@ class ClientBase: "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status, data=data, ) @@ -595,6 +695,11 @@ class ClientBase: "supports_embeddings": self.supports_embeddings, "embeddings_status": self.embeddings_status, "embeddings_model_name": self.embeddings_model_name, + "reason_enabled": self.reason_enabled, + "reason_tokens": self.reason_tokens, + "min_reason_tokens": self.min_reason_tokens, + "reason_response_pattern": self.reason_response_pattern, + "requires_reasoning_pattern": self.requires_reasoning_pattern, "request_information": self.request_information.model_dump() if self.request_information else None, @@ -646,20 +751,16 @@ class ClientBase: return try: - self.model_name = await self.get_model_name() + self.remote_model_name = await self.get_model_name() except Exception as e: self.log.warning("client status error", e=e, client=self.name) - self.model_name = None + self.remote_model_name = None self.connected = False self.emit_status() return self.connected = True - if not self.model_name or self.model_name == "None": - self.emit_status() - return - self.emit_status() def generate_prompt_parameters(self, kind: str): @@ -682,6 +783,15 @@ class ClientBase: parameters, kind, agent_context.action ) + if self.reason_enabled and self.reason_tokens > 0: + log.debug( + "padding for reasoning", + client=self.client_type, + reason_tokens=self.reason_tokens, + validated_reason_tokens=self.validated_reason_tokens, + ) + parameters["max_tokens"] += self.validated_reason_tokens + if client_context_attribute( "nuke_repetition" ) > 0.0 and self.jiggle_enabled_for(kind): @@ -838,12 +948,89 @@ class ClientBase: else: self.request_information.tokens += tokens + def strip_coercion_prompt(self, response: str, coercion_prompt: str = None) -> str: + """ + Strips the coercion prompt from the response if it is present. + """ + if not coercion_prompt or not response.startswith(coercion_prompt): + return response + + return response.replace(coercion_prompt, "").lstrip() + + def strip_reasoning(self, response: str) -> tuple[str, str]: + """ + Strips the reasoning from the response if the model is reasoning. + """ + + if not self.reason_enabled: + return response, None + + if not self.requires_reasoning_pattern: + # reasoning handled automatically during streaming + return response, None + + pattern = self.reason_response_pattern + if not pattern: + pattern = DEFAULT_REASONING_PATTERN + + log.debug("reasoning pattern", pattern=pattern) + + extract_reason = re.search(pattern, response, re.DOTALL) + + if extract_reason: + reasoning_response = extract_reason.group(0) + return response.replace(reasoning_response, ""), reasoning_response + + raise ReasoningResponseError() + + def attach_response_length_instruction( + self, prompt: str, response_length: int | None + ) -> str: + """ + Attaches the response length instruction to the prompt. + """ + + if not response_length or response_length < 0: + log.warning("response length instruction", response_length=response_length) + return prompt + + instructions_prompt = Prompt.get( + "common.response-length", + vars={ + "response_length": response_length, + "attach_response_length_instruction": True, + }, + ) + + instructions_prompt = instructions_prompt.render() + + if instructions_prompt.strip() in prompt: + log.debug( + "response length instruction already in prompt", + instructions_prompt=instructions_prompt, + ) + return prompt + + log.debug( + "response length instruction", instructions_prompt=instructions_prompt + ) + + if "<|RESPONSE_LENGTH_INSTRUCTIONS|>" in prompt: + return prompt.replace( + "<|RESPONSE_LENGTH_INSTRUCTIONS|>", instructions_prompt + ) + elif "<|BOT|>" in prompt: + return prompt.replace("<|BOT|>", f"{instructions_prompt}<|BOT|>") + else: + return f"{prompt}{instructions_prompt}" + async def send_prompt( self, prompt: str, kind: str = "conversation", finalize: Callable = lambda x: x, retries: int = 2, + data_expected: bool | None = None, ) -> str: """ Send a prompt to the AI and return its response. @@ -852,7 +1039,9 @@ class ClientBase: """ try: - return await self._send_prompt(prompt, kind, finalize, retries) + return await self._send_prompt( + prompt, kind, finalize, retries, data_expected + ) except GenerationCancelled: await self.abort_generation() raise @@ -863,6 +1052,7 @@ class ClientBase: kind: str = "conversation", finalize: Callable = lambda x: x, retries: int = 2, + data_expected: bool | None = None, ) -> str: """ Send a prompt to the AI and return its response. @@ -871,6 +1061,7 @@ class ClientBase: """ try: + self.rate_limit_update() if self.rate_limit_counter: aborted: bool = False while not self.rate_limit_counter.increment(): @@ -927,12 +1118,27 @@ class ClientBase: try: self._returned_prompt_tokens = None self._returned_response_tokens = None + self._reasoning_response = None self.emit_status(processing=True) await self.status() prompt_param = self.generate_prompt_parameters(kind) + if self.reason_enabled and not data_expected: + prompt = self.attach_response_length_instruction( + prompt, + (prompt_param.get(self.max_tokens_param_name) or 0) + - self.reason_tokens, + ) + + if not self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + if coercion_prompt: + prompt += f"{INDIRECT_COERCION_PROMPT}{coercion_prompt}" + else: + coercion_prompt = None + finalized_prompt = self.prompt_template( self.get_system_message(kind), prompt ).strip(" ") @@ -954,11 +1160,26 @@ class ClientBase: max_token_length=self.max_token_length, parameters=prompt_param, ) - prompt_sent = self.repetition_adjustment(finalized_prompt) + + if "<|RESPONSE_LENGTH_INSTRUCTIONS|>" in finalized_prompt: + finalized_prompt = finalized_prompt.replace( + "\n<|RESPONSE_LENGTH_INSTRUCTIONS|>", "" + ) self.new_request() - response = await self._cancelable_generate(prompt_sent, prompt_param, kind) + response = await self._cancelable_generate( + finalized_prompt, prompt_param, kind + ) + + response, reasoning_response = self.strip_reasoning(response) + if reasoning_response: + self._reasoning_response = reasoning_response + + if coercion_prompt: + response = self.process_response_for_indirect_coercion( + finalized_prompt, response, coercion_prompt + ) self.end_request() @@ -966,14 +1187,13 @@ class ClientBase: # generation was cancelled raise response - # response = await self.generate(prompt_sent, prompt_param, kind) - - response, finalized_prompt = await self.auto_break_repetition( - finalized_prompt, prompt_param, response, kind, retries - ) - if REPLACE_SMART_QUOTES: - response = response.replace("“", '"').replace("”", '"') + response = ( + response.replace("“", '"') + .replace("”", '"') + .replace("‘", "'") + .replace("’", "'") + ) time_end = time.time() @@ -991,7 +1211,7 @@ class ClientBase: "prompt_sent", data=PromptData( kind=kind, - prompt=prompt_sent, + prompt=finalized_prompt, response=response, prompt_tokens=self._returned_prompt_tokens or token_length, response_tokens=self._returned_response_tokens @@ -1003,12 +1223,17 @@ class ClientBase: generation_parameters=prompt_param, inference_preset=client_context_attribute("inference_preset"), preset_group=self.preset_group, + reasoning=self._reasoning_response, ).model_dump(), ) return response except GenerationCancelled: raise + except GenerationProcessingError as e: + self.log.error("send_prompt error", e=e) + emit("status", message=str(e), status="error") + return "" except Exception: self.log.error("send_prompt error", e=traceback.format_exc()) emit( @@ -1023,130 +1248,6 @@ class ClientBase: if self.rate_limit_counter: self.rate_limit_counter.increment() - async def auto_break_repetition( - self, - finalized_prompt: str, - prompt_param: dict, - response: str, - kind: str, - retries: int, - pad_max_tokens: int = 32, - ) -> str: - """ - If repetition breaking is enabled, this will retry the prompt if its - response is too similar to other messages in the prompt - - This requires the agent to have the allow_repetition_break method - and the jiggle_enabled_for method and the client to have the - auto_break_repetition_enabled attribute set to True - - Arguments: - - - finalized_prompt: the prompt that was sent - - prompt_param: the parameters that were used - - response: the response that was received - - kind: the kind of generation - - retries: the number of retries left - - pad_max_tokens: increase response max_tokens by this amount per iteration - - Returns: - - - the response - """ - - if not self.auto_break_repetition_enabled or not response.strip(): - return response, finalized_prompt - - agent_context = active_agent.get() - if self.jiggle_enabled_for(kind, auto=True): - # check if the response is a repetition - # using the default similarity threshold of 98, meaning it needs - # to be really similar to be considered a repetition - - is_repetition, similarity_score, matched_line = util.similarity_score( - response, finalized_prompt.split("\n"), similarity_threshold=80 - ) - - if not is_repetition: - # not a repetition, return the response - - self.log.debug( - "send_prompt no similarity", similarity_score=similarity_score - ) - finalized_prompt = self.repetition_adjustment( - finalized_prompt, is_repetitive=False - ) - return response, finalized_prompt - - while is_repetition and retries > 0: - # it's a repetition, retry the prompt with adjusted parameters - - self.log.warn( - "send_prompt similarity retry", - agent=agent_context.agent.agent_type, - similarity_score=similarity_score, - retries=retries, - ) - - # first we apply the client's randomness jiggle which will adjust - # parameters like temperature and repetition_penalty, depending - # on the client - # - # this is a cumulative adjustment, so it will add to the previous - # iteration's adjustment, this also means retries should be kept low - # otherwise it will get out of hand and start generating nonsense - - self.jiggle_randomness(prompt_param, offset=0.5) - - # then we pad the max_tokens by the pad_max_tokens amount - - prompt_param[self.max_tokens_param_name] += pad_max_tokens - - # send the prompt again - # we use the repetition_adjustment method to further encourage - # the AI to break the repetition on its own as well. - - finalized_prompt = self.repetition_adjustment( - finalized_prompt, is_repetitive=True - ) - - response = retried_response = await self.generate( - finalized_prompt, prompt_param, kind - ) - - self.log.debug( - "send_prompt dedupe sentences", - response=response, - matched_line=matched_line, - ) - - # a lot of the times the response will now contain the repetition + something new - # so we dedupe the response to remove the repetition on sentences level - - response = util.dedupe_sentences( - response, matched_line, similarity_threshold=85, debug=True - ) - self.log.debug( - "send_prompt dedupe sentences (after)", response=response - ) - - # deduping may have removed the entire response, so we check for that - - if not util.strip_partial_sentences(response).strip(): - # if the response is empty, we set the response to the original - # and try again next loop - - response = retried_response - - # check if the response is a repetition again - - is_repetition, similarity_score, matched_line = util.similarity_score( - response, finalized_prompt.split("\n"), similarity_threshold=80 - ) - retries -= 1 - - return response, finalized_prompt - def count_tokens(self, content: str): return util.count_tokens(content) @@ -1169,31 +1270,9 @@ class ClientBase: return agent.allow_repetition_break(kind, agent_context.action, auto=auto) - def repetition_adjustment(self, prompt: str, is_repetitive: bool = False): - """ - Breaks the prompt into lines and checkse each line for a match with - [$REPETITION|{repetition_adjustment}]. - - On match and if is_repetitive is True, the line is removed from the prompt and - replaced with the repetition_adjustment. - - On match and if is_repetitive is False, the line is removed from the prompt. - """ - - lines = prompt.split("\n") - new_lines = [] - for line in lines: - if line.startswith("[$REPETITION|"): - if is_repetitive: - new_lines.append(line.split("|")[1][:-1]) - else: - new_lines.append("") - else: - new_lines.append(line) - - return "\n".join(new_lines) - - def process_response_for_indirect_coercion(self, prompt: str, response: str) -> str: + def process_response_for_indirect_coercion( + self, prompt: str, response: str, coercion_prompt: str + ) -> str: """ A lot of remote APIs don't let us control the prompt template and we cannot directly append the beginning of the desired response to the prompt. @@ -1202,13 +1281,19 @@ class ClientBase: and then hopefully it will adhere to it and we can strip it off the actual response. """ - _, right = prompt.split("\nStart your response with: ") - expected_response = right.strip() - if expected_response and expected_response.startswith("{"): + if coercion_prompt and coercion_prompt.startswith("{"): if response.startswith("```json") and response.endswith("```"): response = response[7:-3].strip() - if right and response.startswith(right): - response = response[len(right) :].strip() + log.debug( + "process_response_for_indirect_coercion", + response=f"|{response[:100]}...|", + coercion_prompt=f"|{coercion_prompt}|", + ) + + if coercion_prompt and response.startswith(coercion_prompt): + response = response[len(coercion_prompt) :].strip() + elif coercion_prompt and response.lstrip().startswith(coercion_prompt): + response = response.lstrip()[len(coercion_prompt) :].strip() return response diff --git a/src/talemate/client/cohere.py b/src/talemate/client/cohere.py index 06fe26c4..730e64c6 100644 --- a/src/talemate/client/cohere.py +++ b/src/talemate/client/cohere.py @@ -15,9 +15,8 @@ from talemate.client.remote import ( EndpointOverrideMixin, endpoint_override_extra_fields, ) -from talemate.config import Client as BaseClientConfig, load_config +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit -from talemate.emit.signals import handlers from talemate.util import count_tokens __all__ = [ @@ -54,7 +53,6 @@ class CohereClient(EndpointOverrideMixin, ClientBase): client_type = "cohere" conversation_retries = 0 - auto_break_repetition_enabled = False decensor_enabled = True config_cls = ClientConfig @@ -67,18 +65,9 @@ class CohereClient(EndpointOverrideMixin, ClientBase): extra_fields: dict[str, ExtraField] = endpoint_override_extra_fields() defaults: Defaults = Defaults() - def __init__(self, model="command-r-plus", **kwargs): - self.model_name = model - self.api_key_status = None - self._reconfigure_endpoint_override(**kwargs) - self.config = load_config() - super().__init__(**kwargs) - - handlers["config_saved"].connect(self.on_config_saved) - @property def cohere_api_key(self): - return self.config.get("cohere", {}).get("api_key") + return self.config.cohere.api_key @property def supported_parameters(self): @@ -96,15 +85,15 @@ class CohereClient(EndpointOverrideMixin, ClientBase): def emit_status(self, processing: bool = None): error_action = None + error_message = None if processing is not None: self.processing = processing if self.cohere_api_key: status = "busy" if self.processing else "idle" - model_name = self.model_name else: status = "error" - model_name = "No API key set" + error_message = "No API key set" error_action = ErrorAction( title="Set API Key", action_name="openAppConfig", @@ -117,7 +106,7 @@ class CohereClient(EndpointOverrideMixin, ClientBase): if not self.model_name: status = "error" - model_name = "No model loaded" + error_message = "No model loaded" self.current_status = status @@ -125,67 +114,18 @@ class CohereClient(EndpointOverrideMixin, ClientBase): "error_action": error_action.model_dump() if error_action else None, "meta": self.Meta().model_dump(), "enabled": self.enabled, + "error_message": error_message, } data.update(self._common_status_data()) emit( "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status if self.enabled else "disabled", data=data, ) - def set_client(self, max_token_length: int = None): - if not self.cohere_api_key and not self.endpoint_override_base_url_configured: - self.client = AsyncClientV2("sk-1111") - log.error("No cohere API key set") - if self.api_key_status: - self.api_key_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = "command-r-plus" - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - - model = self.model_name - - self.client = AsyncClientV2(self.api_key, base_url=self.base_url) - self.max_token_length = max_token_length or 16384 - - if not self.api_key_status: - if self.api_key_status is False: - emit("request_client_status") - emit("request_agent_status") - self.api_key_status = True - - log.info( - "cohere set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=model, - ) - - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - self._reconfigure_common_parameters(**kwargs) - self._reconfigure_endpoint_override(**kwargs) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - def response_tokens(self, response: str): return count_tokens(response) @@ -195,16 +135,6 @@ class CohereClient(EndpointOverrideMixin, ClientBase): async def status(self): self.emit_status() - def prompt_template(self, system_message: str, prompt: str): - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - - return prompt - def clean_prompt_parameters(self, parameters: dict): super().clean_prompt_parameters(parameters) @@ -228,13 +158,7 @@ class CohereClient(EndpointOverrideMixin, ClientBase): if not self.cohere_api_key and not self.endpoint_override_base_url_configured: raise Exception("No cohere API key set") - right = None - expected_response = None - try: - _, right = prompt.split("\nStart your response with: ") - expected_response = right.strip() - except (IndexError, ValueError): - pass + client = AsyncClientV2(self.api_key, base_url=self.base_url) human_message = prompt.strip() system_message = self.get_system_message(kind) @@ -263,7 +187,7 @@ class CohereClient(EndpointOverrideMixin, ClientBase): # manager, so attempting to use `async with` raises a `TypeError` as seen # in issue logs. We therefore iterate over the generator directly. - stream = self.client.chat_stream( + stream = client.chat_stream( model=self.model_name, messages=messages, **parameters, @@ -283,13 +207,6 @@ class CohereClient(EndpointOverrideMixin, ClientBase): log.debug("generated response", response=response) - if expected_response and expected_response.startswith("{"): - if response.startswith("```json") and response.endswith("```"): - response = response[7:-3].strip() - - if right and response.startswith(right): - response = response[len(right) :].strip() - return response # except PermissionDeniedError as e: # self.log.error("generate error", e=e) diff --git a/src/talemate/client/deepseek.py b/src/talemate/client/deepseek.py index dfa536a5..302cc27a 100644 --- a/src/talemate/client/deepseek.py +++ b/src/talemate/client/deepseek.py @@ -4,9 +4,7 @@ from openai import AsyncOpenAI, PermissionDeniedError from talemate.client.base import ClientBase, ErrorAction, CommonDefaults from talemate.client.registry import register -from talemate.config import load_config from talemate.emit import emit -from talemate.emit.signals import handlers from talemate.util import count_tokens __all__ = [ @@ -40,7 +38,6 @@ class DeepSeekClient(ClientBase): client_type = "deepseek" conversation_retries = 0 - auto_break_repetition_enabled = False # TODO: make this configurable? decensor_enabled = False @@ -52,17 +49,9 @@ class DeepSeekClient(ClientBase): requires_prompt_template: bool = False defaults: Defaults = Defaults() - def __init__(self, model="deepseek-chat", **kwargs): - self.model_name = model - self.api_key_status = None - self.config = load_config() - super().__init__(**kwargs) - - handlers["config_saved"].connect(self.on_config_saved) - @property def deepseek_api_key(self): - return self.config.get("deepseek", {}).get("api_key") + return self.config.deepseek.api_key @property def supported_parameters(self): @@ -75,15 +64,15 @@ class DeepSeekClient(ClientBase): def emit_status(self, processing: bool = None): error_action = None + error_message = None if processing is not None: self.processing = processing if self.deepseek_api_key: status = "busy" if self.processing else "idle" - model_name = self.model_name else: status = "error" - model_name = "No API key set" + error_message = "No API key set" error_action = ErrorAction( title="Set API Key", action_name="openAppConfig", @@ -96,7 +85,7 @@ class DeepSeekClient(ClientBase): if not self.model_name: status = "error" - model_name = "No model loaded" + error_message = "No model loaded" self.current_status = status @@ -104,66 +93,18 @@ class DeepSeekClient(ClientBase): "error_action": error_action.model_dump() if error_action else None, "meta": self.Meta().model_dump(), "enabled": self.enabled, + "error_message": error_message, } data.update(self._common_status_data()) emit( "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status if self.enabled else "disabled", data=data, ) - def set_client(self, max_token_length: int = None): - if not self.deepseek_api_key: - self.client = AsyncOpenAI(api_key="sk-1111", base_url=BASE_URL) - log.error("No DeepSeek API key set") - if self.api_key_status: - self.api_key_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = "deepseek-chat" - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - - model = self.model_name - - self.client = AsyncOpenAI(api_key=self.deepseek_api_key, base_url=BASE_URL) - self.max_token_length = max_token_length or 16384 - - if not self.api_key_status: - if self.api_key_status is False: - emit("request_client_status") - emit("request_agent_status") - self.api_key_status = True - - log.info( - "deepseek set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=model, - ) - - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - self._reconfigure_common_parameters(**kwargs) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - def count_tokens(self, content: str): if not self.model_name: return 0 @@ -172,18 +113,6 @@ class DeepSeekClient(ClientBase): async def status(self): self.emit_status() - def prompt_template(self, system_message: str, prompt: str): - # only gpt-4-1106-preview supports json_object response coersion - - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - - return prompt - def response_tokens(self, response: str): # Count tokens in a response string using the util.count_tokens helper return self.count_tokens(response) @@ -200,20 +129,7 @@ class DeepSeekClient(ClientBase): if not self.deepseek_api_key: raise Exception("No DeepSeek API key set") - # only gpt-4-* supports enforcing json object - supports_json_object = ( - self.model_name.startswith("gpt-4-") - or self.model_name in JSON_OBJECT_RESPONSE_MODELS - ) - right = None - expected_response = None - try: - _, right = prompt.split("\nStart your response with: ") - expected_response = right.strip() - if expected_response.startswith("{") and supports_json_object: - parameters["response_format"] = {"type": "json_object"} - except (IndexError, ValueError): - pass + client = AsyncOpenAI(api_key=self.deepseek_api_key, base_url=BASE_URL) human_message = {"role": "user", "content": prompt.strip()} system_message = {"role": "system", "content": self.get_system_message(kind)} @@ -227,7 +143,7 @@ class DeepSeekClient(ClientBase): try: # Use streaming so we can update_Request_tokens incrementally - stream = await self.client.chat.completions.create( + stream = await client.chat.completions.create( model=self.model_name, messages=[system_message, human_message], stream=True, @@ -251,20 +167,6 @@ class DeepSeekClient(ClientBase): self._returned_prompt_tokens = self.prompt_tokens(prompt) self._returned_response_tokens = self.response_tokens(response) - # older models don't support json_object response coersion - # and often like to return the response wrapped in ```json - # so we strip that out if the expected response is a json object - if ( - not supports_json_object - and expected_response - and expected_response.startswith("{") - ): - if response.startswith("```json") and response.endswith("```"): - response = response[7:-3].strip() - - if right and response.startswith(right): - response = response[len(right) :].strip() - return response except PermissionDeniedError as e: self.log.error("generate error", e=e) diff --git a/src/talemate/client/google.py b/src/talemate/client/google.py index faebddc1..6f7fc14b 100644 --- a/src/talemate/client/google.py +++ b/src/talemate/client/google.py @@ -21,10 +21,8 @@ from talemate.client.remote import ( EndpointOverrideMixin, endpoint_override_extra_fields, ) -from talemate.config import Client as BaseClientConfig -from talemate.config import load_config +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit -from talemate.emit.signals import handlers from talemate.util import count_tokens __all__ = [ @@ -41,10 +39,11 @@ SUPPORTED_MODELS = [ "gemini-1.5-pro", "gemini-2.0-flash", "gemini-2.0-flash-lite", - "gemini-2.5-flash-preview-04-17", + "gemini-2.5-flash-lite-preview-06-17", "gemini-2.5-flash-preview-05-20", - "gemini-2.5-pro-preview-03-25", + "gemini-2.5-flash", "gemini-2.5-pro-preview-06-05", + "gemini-2.5-pro", ] @@ -59,6 +58,9 @@ class ClientConfig(EndpointOverride, BaseClientConfig): disable_safety_settings: bool = False +MIN_THINKING_TOKENS = 0 + + @register() class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): """ @@ -67,7 +69,6 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): client_type = "google" conversation_retries = 0 - auto_break_repetition_enabled = False decensor_enabled = True config_cls = ClientConfig @@ -90,21 +91,23 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): extra_fields.update(endpoint_override_extra_fields()) def __init__(self, model="gemini-2.0-flash", **kwargs): - self.model_name = model self.setup_status = None self.model_instance = None - self.disable_safety_settings = kwargs.get("disable_safety_settings", False) self.google_credentials_read = False self.google_project_id = None - self._reconfigure_endpoint_override(**kwargs) - self.config = load_config() super().__init__(**kwargs) - handlers["config_saved"].connect(self.on_config_saved) + @property + def disable_safety_settings(self): + return self.client_config.disable_safety_settings + + @property + def min_reason_tokens(self) -> int: + return MIN_THINKING_TOKENS @property def can_be_coerced(self) -> bool: - return True + return not self.reason_enabled @property def google_credentials(self): @@ -116,15 +119,15 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): @property def google_credentials_path(self): - return self.config.get("google").get("gcloud_credentials_path") + return self.config.google.gcloud_credentials_path @property def google_location(self): - return self.config.get("google").get("gcloud_location") + return self.config.google.gcloud_location @property def google_api_key(self): - return self.config.get("google").get("api_key") + return self.config.google.api_key @property def vertexai_ready(self) -> bool: @@ -197,6 +200,16 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): return genai_types.HttpOptions(base_url=self.base_url) + @property + def thinking_config(self) -> genai_types.ThinkingConfig | None: + if not self.reason_enabled: + return None + + return genai_types.ThinkingConfig( + thinking_budget=self.validated_reason_tokens, + include_thoughts=True, + ) + @property def supported_parameters(self): return [ @@ -211,6 +224,10 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): ), ] + @property + def requires_reasoning_pattern(self) -> bool: + return False + def emit_status(self, processing: bool = None): error_action = None if processing is not None: @@ -269,46 +286,20 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): "Error setting client base URL", error=e, client=self.client_type ) - def set_client(self, max_token_length: int = None, **kwargs): - if not self.ready: - log.error("Google cloud setup incomplete") - if self.setup_status: - self.setup_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = "gemini-2.0-flash" - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - + def make_client(self) -> genai.Client: if self.google_credentials_path: os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = self.google_credentials_path - - model = self.model_name - - self.max_token_length = max_token_length or 16384 - if self.vertexai_ready and not self.developer_api_ready: - self.client = genai.Client( + return genai.Client( vertexai=True, project=self.google_project_id, location=self.google_location, ) else: - self.client = genai.Client( + return genai.Client( api_key=self.api_key or None, http_options=self.http_options ) - log.info( - "google set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=model, - ) - def response_tokens(self, response: str): """Return token count for a response which may be a string or SDK object.""" return count_tokens(response) @@ -316,22 +307,6 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): def prompt_tokens(self, prompt: str): return count_tokens(prompt) - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - if "disable_safety_settings" in kwargs: - self.disable_safety_settings = kwargs["disable_safety_settings"] - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - if "double_coercion" in kwargs: - self.double_coercion = kwargs["double_coercion"] - - self._reconfigure_common_parameters(**kwargs) - def clean_prompt_parameters(self, parameters: dict): super().clean_prompt_parameters(parameters) @@ -339,13 +314,6 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): if "top_k" in parameters and parameters["top_k"] == 0: del parameters["top_k"] - def prompt_template(self, system_message: str, prompt: str): - """ - Google handles the prompt template internally, so we just - give the prompt as is. - """ - return prompt - async def generate(self, prompt: str, parameters: dict, kind: str): """ Generates text from the given prompt and parameters. @@ -354,7 +322,12 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): if not self.ready: raise Exception("Google setup incomplete") - prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + client = self.make_client() + + if self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + else: + coercion_prompt = None human_message = prompt.strip() system_message = self.get_system_message(kind) @@ -371,6 +344,7 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): ] if coercion_prompt: + log.debug("Adding coercion pre-fill", coercion_prompt=coercion_prompt) contents.append( genai_types.Content( role="model", @@ -384,49 +358,57 @@ class GoogleClient(EndpointOverrideMixin, RemoteServiceMixin, ClientBase): self.log.debug( "generate", + model=self.model_name, base_url=self.base_url, prompt=prompt[:128] + " ...", parameters=parameters, system_message=system_message, disable_safety_settings=self.disable_safety_settings, safety_settings=self.safety_settings, + thinking_config=self.thinking_config, ) try: # Use streaming so we can update_Request_tokens incrementally - # stream = await chat.send_message_async( - # human_message, - # safety_settings=self.safety_settings, - # generation_config=parameters, - # stream=True - # ) - - stream = await self.client.aio.models.generate_content_stream( + stream = await client.aio.models.generate_content_stream( model=self.model_name, contents=contents, config=genai_types.GenerateContentConfig( safety_settings=self.safety_settings, http_options=self.http_options, + thinking_config=self.thinking_config, **parameters, ), ) response = "" - + reasoning = "" + # https://ai.google.dev/gemini-api/docs/thinking#summaries async for chunk in stream: - # For each streamed chunk, append content and update token counts - content_piece = getattr(chunk, "text", None) - if not content_piece: - # Some SDK versions wrap text under candidates[0].text - try: - content_piece = chunk.candidates[0].text # type: ignore - except Exception: - content_piece = None + try: + if not chunk: + continue - if content_piece: - response += content_piece - # Incrementally update token usage - self.update_request_tokens(count_tokens(content_piece)) + if not chunk.candidates: + continue + + if not chunk.candidates[0].content.parts: + continue + + for part in chunk.candidates[0].content.parts: + if not part.text: + continue + if part.thought: + reasoning += part.text + else: + response += part.text + self.update_request_tokens(count_tokens(part.text)) + except Exception as e: + log.error("error processing chunk", e=e, chunk=chunk) + continue + + if reasoning: + self._reasoning_response = reasoning # Store total token accounting for prompt/response self._returned_prompt_tokens = self.prompt_tokens(prompt) diff --git a/src/talemate/client/groq.py b/src/talemate/client/groq.py index b9e52569..8e42c886 100644 --- a/src/talemate/client/groq.py +++ b/src/talemate/client/groq.py @@ -4,9 +4,8 @@ from groq import AsyncGroq, PermissionDeniedError from talemate.client.base import ClientBase, ErrorAction, ParameterReroute, ExtraField from talemate.client.registry import register -from talemate.config import load_config +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit -from talemate.emit.signals import handlers from talemate.client.remote import ( EndpointOverride, EndpointOverrideMixin, @@ -23,6 +22,10 @@ SUPPORTED_MODELS = [ "mixtral-8x7b-32768", "llama3-8b-8192", "llama3-70b-8192", + "llama-3.3-70b-versatile", + "qwen/qwen3-32b", + "moonshotai/kimi-k2-instruct", + "deepseek-r1-distill-llama-70b", ] JSON_OBJECT_RESPONSE_MODELS = [] @@ -30,7 +33,11 @@ JSON_OBJECT_RESPONSE_MODELS = [] class Defaults(EndpointOverride, pydantic.BaseModel): max_token_length: int = 8192 - model: str = "llama3-70b-8192" + model: str = "moonshotai/kimi-k2-instruct" + + +class ClientConfig(EndpointOverride, BaseClientConfig): + pass @register() @@ -41,9 +48,9 @@ class GroqClient(EndpointOverrideMixin, ClientBase): client_type = "groq" conversation_retries = 0 - auto_break_repetition_enabled = False # TODO: make this configurable? decensor_enabled = True + config_cls = ClientConfig class Meta(ClientBase.Meta): name_prefix: str = "Groq" @@ -54,19 +61,13 @@ class GroqClient(EndpointOverrideMixin, ClientBase): defaults: Defaults = Defaults() extra_fields: dict[str, ExtraField] = endpoint_override_extra_fields() - def __init__(self, model="llama3-70b-8192", **kwargs): - self.model_name = model - self.api_key_status = None - # Apply any endpoint override parameters provided via kwargs before creating client - self._reconfigure_endpoint_override(**kwargs) - self.config = load_config() - super().__init__(**kwargs) - - handlers["config_saved"].connect(self.on_config_saved) + @property + def can_be_coerced(self) -> bool: + return not self.reason_enabled @property def groq_api_key(self): - return self.config.get("groq", {}).get("api_key") + return self.config.groq.api_key @property def supported_parameters(self): @@ -83,15 +84,15 @@ class GroqClient(EndpointOverrideMixin, ClientBase): def emit_status(self, processing: bool = None): error_action = None + error_message = None if processing is not None: self.processing = processing if self.groq_api_key: status = "busy" if self.processing else "idle" - model_name = self.model_name else: status = "error" - model_name = "No API key set" + error_message = "No API key set" error_action = ErrorAction( title="Set API Key", action_name="openAppConfig", @@ -104,7 +105,7 @@ class GroqClient(EndpointOverrideMixin, ClientBase): if not self.model_name: status = "error" - model_name = "No model loaded" + error_message = "No model loaded" self.current_status = status @@ -112,6 +113,7 @@ class GroqClient(EndpointOverrideMixin, ClientBase): "error_action": error_action.model_dump() if error_action else None, "meta": self.Meta().model_dump(), "enabled": self.enabled, + "error_message": error_message, } # Include shared/common status data (rate limit, etc.) data.update(self._common_status_data()) @@ -120,66 +122,11 @@ class GroqClient(EndpointOverrideMixin, ClientBase): "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status if self.enabled else "disabled", data=data, ) - def set_client(self, max_token_length: int = None): - # Determine if we should use the globally configured API key or the override key - if not self.groq_api_key and not self.endpoint_override_base_url_configured: - # No API key and no endpoint override – cannot initialize client correctly - self.client = AsyncGroq(api_key="sk-1111") - log.error("No groq.ai API key set") - if self.api_key_status: - self.api_key_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = "llama3-70b-8192" - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - - model = self.model_name - - # Use the override values (if any) when constructing the Groq client - self.client = AsyncGroq(api_key=self.api_key, base_url=self.base_url) - self.max_token_length = max_token_length or 16384 - - if not self.api_key_status: - if self.api_key_status is False: - emit("request_client_status") - emit("request_agent_status") - self.api_key_status = True - - log.info( - "groq.ai set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=model, - ) - - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - # Allow dynamic reconfiguration of endpoint override parameters - self._reconfigure_endpoint_override(**kwargs) - # Reconfigure any common parameters (rate limit, data format, etc.) - self._reconfigure_common_parameters(**kwargs) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - def response_tokens(self, response: str): return response.usage.completion_tokens @@ -189,16 +136,6 @@ class GroqClient(EndpointOverrideMixin, ClientBase): async def status(self): self.emit_status() - def prompt_template(self, system_message: str, prompt: str): - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - - return prompt - async def generate(self, prompt: str, parameters: dict, kind: str): """ Generates text from the given prompt and parameters. @@ -207,16 +144,12 @@ class GroqClient(EndpointOverrideMixin, ClientBase): if not self.groq_api_key and not self.endpoint_override_base_url_configured: raise Exception("No groq.ai API key set") - supports_json_object = self.model_name in JSON_OBJECT_RESPONSE_MODELS - right = None - expected_response = None - try: - _, right = prompt.split("\nStart your response with: ") - expected_response = right.strip() - if expected_response.startswith("{") and supports_json_object: - parameters["response_format"] = {"type": "json_object"} - except (IndexError, ValueError): - pass + client = AsyncGroq(api_key=self.api_key, base_url=self.base_url) + + if self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + else: + coercion_prompt = None system_message = self.get_system_message(kind) @@ -225,6 +158,10 @@ class GroqClient(EndpointOverrideMixin, ClientBase): {"role": "user", "content": prompt}, ] + if coercion_prompt: + log.debug("Adding coercion pre-fill", coercion_prompt=coercion_prompt) + messages.append({"role": "assistant", "content": coercion_prompt.strip()}) + self.log.debug( "generate", prompt=prompt[:128] + " ...", @@ -233,27 +170,25 @@ class GroqClient(EndpointOverrideMixin, ClientBase): ) try: - response = await self.client.chat.completions.create( + stream = await client.chat.completions.create( model=self.model_name, messages=messages, + stream=True, **parameters, ) - response = response.choices[0].message.content + response = "" - # older models don't support json_object response coersion - # and often like to return the response wrapped in ```json - # so we strip that out if the expected response is a json object - if ( - not supports_json_object - and expected_response - and expected_response.startswith("{") - ): - if response.startswith("```json") and response.endswith("```"): - response = response[7:-3].strip() - - if right and response.startswith(right): - response = response[len(right) :].strip() + # Iterate over streamed chunks + async for chunk in stream: + if not chunk.choices: + continue + delta = chunk.choices[0].delta + if delta and getattr(delta, "content", None): + content_piece = delta.content + response += content_piece + # Incrementally track token usage + self.update_request_tokens(self.count_tokens(content_piece)) return response except PermissionDeniedError as e: diff --git a/src/talemate/client/koboldcpp.py b/src/talemate/client/koboldcpp.py index 32b1e539..cedc927a 100644 --- a/src/talemate/client/koboldcpp.py +++ b/src/talemate/client/koboldcpp.py @@ -75,6 +75,7 @@ class KoboldEmbeddingFunction(EmbeddingFunction): class KoboldCppClient(ClientBase): auto_determine_prompt_template: bool = True client_type = "koboldcpp" + remote_model_locked: bool = True class Meta(ClientBase.Meta): name_prefix: str = "KoboldCpp" @@ -188,6 +189,10 @@ class KoboldCppClient(ClientBase): def embeddings_function(self): return KoboldEmbeddingFunction(self.embeddings_url, self.embeddings_model_name) + @property + def default_prompt_template(self) -> str: + return "KoboldAI.jinja2" + def api_endpoint_specified(self, url: str) -> bool: return "/v1" in self.api_url @@ -200,14 +205,9 @@ class KoboldCppClient(ClientBase): self.api_url += "/" def __init__(self, **kwargs): - self.api_key = kwargs.pop("api_key", "") super().__init__(**kwargs) self.ensure_api_endpoint_specified() - def set_client(self, **kwargs): - self.api_key = kwargs.get("api_key", self.api_key) - self.ensure_api_endpoint_specified() - async def get_embeddings_model_name(self): # if self._embeddings_model_name is set, return it if self.embeddings_model_name: @@ -245,15 +245,21 @@ class KoboldCppClient(ClientBase): model_name=self.embeddings_model_name, ) - self.set_embeddings() + await self.set_embeddings() - await async_signals.get("client.embeddings_available").send( - ClientEmbeddingsStatus( - client=self, - embedding_name=self.embeddings_model_name, - ) + emission = ClientEmbeddingsStatus( + client=self, + embedding_name=self.embeddings_model_name, ) + await async_signals.get("client.embeddings_available").send(emission) + + if not emission.seen: + # the suggestion has not been seen by the memory agent + # yet, so we unset the embeddings model name so it will + # get suggested again + self._embeddings_model_name = None + async def get_model_name(self): self.ensure_api_endpoint_specified() @@ -437,12 +443,6 @@ class KoboldCppClient(ClientBase): except KeyError: pass - def reconfigure(self, **kwargs): - if "api_key" in kwargs: - self.api_key = kwargs.pop("api_key") - - super().reconfigure(**kwargs) - async def visual_automatic1111_setup(self, visual_agent: "VisualBase") -> bool: """ Automatically configure the visual agent for automatic1111 diff --git a/src/talemate/client/lmstudio.py b/src/talemate/client/lmstudio.py index 76682519..5a475d2d 100644 --- a/src/talemate/client/lmstudio.py +++ b/src/talemate/client/lmstudio.py @@ -14,6 +14,7 @@ class Defaults(CommonDefaults, pydantic.BaseModel): class LMStudioClient(ClientBase): auto_determine_prompt_template: bool = True client_type = "lmstudio" + remote_model_locked: bool = True class Meta(ClientBase.Meta): name_prefix: str = "LMStudio" @@ -32,17 +33,16 @@ class LMStudioClient(ClientBase): ), ] - def set_client(self, **kwargs): - self.client = AsyncOpenAI(base_url=self.api_url + "/v1", api_key="sk-1111") - - def reconfigure(self, **kwargs): - super().reconfigure(**kwargs) - - if self.client and self.client.base_url != self.api_url: - self.set_client() + def make_client(self): + return AsyncOpenAI(base_url=self.api_url + "/v1", api_key=self.api_key) async def get_model_name(self): - model_name = await super().get_model_name() + client = self.make_client() + models = await client.models.list(timeout=self.status_request_timeout) + try: + model_name = models.data[0].id + except IndexError: + return None # model name comes back as a file path, so we need to extract the model name # the path could be windows or linux so it needs to handle both backslash and forward slash @@ -65,9 +65,11 @@ class LMStudioClient(ClientBase): parameters=parameters, ) + client = self.make_client() + try: # Send the request in streaming mode so we can update token counts - stream = await self.client.completions.create( + stream = await client.completions.create( model=self.model_name, prompt=prompt, stream=True, diff --git a/src/talemate/client/mistral.py b/src/talemate/client/mistral.py index 11276db8..d623c8e3 100644 --- a/src/talemate/client/mistral.py +++ b/src/talemate/client/mistral.py @@ -15,9 +15,8 @@ from talemate.client.remote import ( EndpointOverrideMixin, endpoint_override_extra_fields, ) -from talemate.config import Client as BaseClientConfig, load_config +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit -from talemate.emit.signals import handlers __all__ = [ "MistralAIClient", @@ -33,14 +32,7 @@ SUPPORTED_MODELS = [ "mistral-small-latest", "mistral-medium-latest", "mistral-large-latest", -] - -JSON_OBJECT_RESPONSE_MODELS = [ - "open-mixtral-8x22b", - "open-mistral-nemo", - "mistral-small-latest", - "mistral-medium-latest", - "mistral-large-latest", + "magistral-medium-2506", ] @@ -61,7 +53,6 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): client_type = "mistral" conversation_retries = 0 - auto_break_repetition_enabled = False # TODO: make this configurable? decensor_enabled = True config_cls = ClientConfig @@ -75,17 +66,13 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): defaults: Defaults = Defaults() extra_fields: dict[str, ExtraField] = endpoint_override_extra_fields() - def __init__(self, model="open-mixtral-8x22b", **kwargs): - self.model_name = model - self.api_key_status = None - self._reconfigure_endpoint_override(**kwargs) - self.config = load_config() - super().__init__(**kwargs) - handlers["config_saved"].connect(self.on_config_saved) + @property + def can_be_coerced(self) -> bool: + return not self.reason_enabled @property def mistral_api_key(self): - return self.config.get("mistralai", {}).get("api_key") + return self.config.mistralai.api_key @property def supported_parameters(self): @@ -97,15 +84,15 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): def emit_status(self, processing: bool = None): error_action = None + error_message = None if processing is not None: self.processing = processing if self.mistral_api_key: status = "busy" if self.processing else "idle" - model_name = self.model_name else: status = "error" - model_name = "No API key set" + error_message = "No API key set" error_action = ErrorAction( title="Set API Key", action_name="openAppConfig", @@ -118,74 +105,25 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): if not self.model_name: status = "error" - model_name = "No model loaded" + error_message = "No model loaded" self.current_status = status data = { "error_action": error_action.model_dump() if error_action else None, "meta": self.Meta().model_dump(), "enabled": self.enabled, + "error_message": error_message, } data.update(self._common_status_data()) emit( "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status if self.enabled else "disabled", data=data, ) - def set_client(self, max_token_length: int = None): - if not self.mistral_api_key and not self.endpoint_override_base_url_configured: - self.client = Mistral(api_key="sk-1111") - log.error("No mistral.ai API key set") - if self.api_key_status: - self.api_key_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = "open-mixtral-8x22b" - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - - model = self.model_name - - self.client = Mistral(api_key=self.api_key, server_url=self.base_url) - self.max_token_length = max_token_length or 16384 - - if not self.api_key_status: - if self.api_key_status is False: - emit("request_client_status") - emit("request_agent_status") - self.api_key_status = True - - log.info( - "mistral.ai set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=model, - ) - - def reconfigure(self, **kwargs): - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - self._reconfigure_common_parameters(**kwargs) - self._reconfigure_endpoint_override(**kwargs) - - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - def response_tokens(self, response: str): return response.usage.completion_tokens @@ -195,16 +133,6 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): async def status(self): self.emit_status() - def prompt_template(self, system_message: str, prompt: str): - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - - return prompt - def clean_prompt_parameters(self, parameters: dict): super().clean_prompt_parameters(parameters) # clamp temperature to 0.1 and 1.0 @@ -220,16 +148,12 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): if not self.mistral_api_key: raise Exception("No mistral.ai API key set") - supports_json_object = self.model_name in JSON_OBJECT_RESPONSE_MODELS - right = None - expected_response = None - try: - _, right = prompt.split("\nStart your response with: ") - expected_response = right.strip() - if expected_response.startswith("{") and supports_json_object: - parameters["response_format"] = {"type": "json_object"} - except (IndexError, ValueError): - pass + client = Mistral(api_key=self.api_key, server_url=self.base_url) + + if self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + else: + coercion_prompt = None system_message = self.get_system_message(kind) @@ -238,6 +162,16 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): {"role": "user", "content": prompt.strip()}, ] + if coercion_prompt: + log.debug("Adding coercion pre-fill", coercion_prompt=coercion_prompt) + messages.append( + { + "role": "assistant", + "content": coercion_prompt.strip(), + "prefix": True, + } + ) + self.log.debug( "generate", base_url=self.base_url, @@ -247,7 +181,7 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): ) try: - event_stream = await self.client.chat.stream_async( + event_stream = await client.chat.stream_async( model=self.model_name, messages=messages, **parameters, @@ -271,22 +205,6 @@ class MistralAIClient(EndpointOverrideMixin, ClientBase): self._returned_prompt_tokens = prompt_tokens self._returned_response_tokens = completion_tokens - # response = response.choices[0].message.content - - # older models don't support json_object response coersion - # and often like to return the response wrapped in ```json - # so we strip that out if the expected response is a json object - if ( - not supports_json_object - and expected_response - and expected_response.startswith("{") - ): - if response.startswith("```json") and response.endswith("```"): - response = response[7:-3].strip() - - if right and response.startswith(right): - response = response[len(right) :].strip() - return response except SDKError as e: self.log.error("generate error", e=e) diff --git a/src/talemate/client/model_prompts.py b/src/talemate/client/model_prompts.py index 042f1c9a..0482fcee 100644 --- a/src/talemate/client/model_prompts.py +++ b/src/talemate/client/model_prompts.py @@ -27,6 +27,8 @@ TALEMATE_TEMPLATE_PATH = os.path.join(BASE_TEMPLATE_PATH, "talemate") # user overrides USER_TEMPLATE_PATH = os.path.join(BASE_TEMPLATE_PATH, "user") +DEFAULT_TEMPLATE = "default.jinja2" + TEMPLATE_IDENTIFIERS = [] @@ -73,10 +75,11 @@ class ModelPrompt: system_message: str, prompt: str, double_coercion: str = None, + default_template: str = DEFAULT_TEMPLATE, ): template, template_file = self.get_template(model_name) if not template: - template_file = "default.jinja2" + template_file = default_template template = self.env.get_template(template_file) if not double_coercion: diff --git a/src/talemate/client/ollama.py b/src/talemate/client/ollama.py index 3836c19f..cf062acf 100644 --- a/src/talemate/client/ollama.py +++ b/src/talemate/client/ollama.py @@ -12,7 +12,7 @@ from talemate.client.base import ( ExtraField, ) from talemate.client.registry import register -from talemate.config import Client as BaseClientConfig +from talemate.config.schema import Client as BaseClientConfig log = structlog.get_logger("talemate.client.ollama") @@ -24,12 +24,10 @@ class OllamaClientDefaults(CommonDefaults): api_url: str = "http://localhost:11434" # Default Ollama URL model: str = "" # Allow empty default, will fetch from Ollama api_handles_prompt_template: bool = False - allow_thinking: bool = False class ClientConfig(BaseClientConfig): api_handles_prompt_template: bool = False - allow_thinking: bool = False @register() @@ -58,13 +56,6 @@ class OllamaClient(ClientBase): required=False, description="Let Ollama handle the prompt template. Only do this if you don't know which prompt template to use. Letting talemate handle the prompt template will generally lead to improved responses.", ), - "allow_thinking": ExtraField( - name="allow_thinking", - type="bool", - label="Allow thinking", - required=False, - description="Allow the model to think before responding. Talemate does not have a good way to deal with this yet, so it's recommended to leave this off.", - ), } @property @@ -90,51 +81,25 @@ class OllamaClient(ClientBase): "extra_stopping_strings", ] + def __init__( + self, + **kwargs, + ): + self._available_models = [] + self._models_last_fetched = 0 + super().__init__(**kwargs) + @property def can_be_coerced(self): """ Determines whether or not his client can pass LLM coercion. (e.g., is able to predefine partial LLM output in the prompt) """ - return not self.api_handles_prompt_template + return not self.api_handles_prompt_template and not self.reason_enabled @property - def can_think(self) -> bool: - """ - Allow reasoning models to think before responding. - """ - return self.allow_thinking - - def __init__( - self, - model=None, - api_handles_prompt_template=False, - allow_thinking=False, - **kwargs, - ): - self.model_name = model - self.api_handles_prompt_template = api_handles_prompt_template - self.allow_thinking = allow_thinking - self._available_models = [] - self._models_last_fetched = 0 - self.client = None - super().__init__(**kwargs) - - def set_client(self, **kwargs): - """ - Initialize the Ollama client with the API URL. - """ - # Update model if provided - if kwargs.get("model"): - self.model_name = kwargs["model"] - - # Create async client with the configured API URL - # Ollama's AsyncClient expects just the base URL without any path - self.client = ollama.AsyncClient(host=self.api_url) - self.api_handles_prompt_template = kwargs.get( - "api_handles_prompt_template", self.api_handles_prompt_template - ) - self.allow_thinking = kwargs.get("allow_thinking", self.allow_thinking) + def api_handles_prompt_template(self) -> bool: + return self.client_config.api_handles_prompt_template async def status(self): """ @@ -177,7 +142,9 @@ class OllamaClient(ClientBase): if time.time() - self._models_last_fetched < FETCH_MODELS_INTERVAL: return self._available_models - response = await self.client.list() + client = ollama.AsyncClient(host=self.api_url) + + response = await client.list() models = response.get("models", []) model_names = [model.model for model in models] self._available_models = sorted(model_names) @@ -192,19 +159,11 @@ class OllamaClient(ClientBase): return data async def get_model_name(self): - return self.model_name + return self.model def prompt_template(self, system_message: str, prompt: str): if not self.api_handles_prompt_template: return super().prompt_template(system_message, prompt) - - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - return prompt def tune_prompt_parameters(self, parameters: dict, kind: str): @@ -251,6 +210,8 @@ class OllamaClient(ClientBase): if not self.model_name: raise Exception("No model specified or available in Ollama") + client = ollama.AsyncClient(host=self.api_url) + # Prepare options for Ollama options = parameters @@ -258,12 +219,11 @@ class OllamaClient(ClientBase): try: # Use generate endpoint for completion - stream = await self.client.generate( + stream = await client.generate( model=self.model_name, prompt=prompt.strip(), options=options, raw=self.can_be_coerced, - think=self.can_think, stream=True, ) @@ -306,20 +266,3 @@ class OllamaClient(ClientBase): prompt_config["repetition_penalty"] = random.uniform( rep_pen + min_offset * 0.3, rep_pen + offset * 0.3 ) - - def reconfigure(self, **kwargs): - """ - Reconfigure the client with new settings. - """ - # Handle model update - if kwargs.get("model"): - self.model_name = kwargs["model"] - - super().reconfigure(**kwargs) - - # Re-initialize client if API URL changed or model changed - if "api_url" in kwargs or "model" in kwargs: - self.set_client(**kwargs) - - if "api_handles_prompt_template" in kwargs: - self.api_handles_prompt_template = kwargs["api_handles_prompt_template"] diff --git a/src/talemate/client/openai.py b/src/talemate/client/openai.py index 81f28289..858f50e3 100644 --- a/src/talemate/client/openai.py +++ b/src/talemate/client/openai.py @@ -12,9 +12,8 @@ from talemate.client.remote import ( EndpointOverrideMixin, endpoint_override_extra_fields, ) -from talemate.config import Client as BaseClientConfig, load_config +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit -from talemate.emit.signals import handlers __all__ = [ "OpenAIClient", @@ -44,22 +43,15 @@ SUPPORTED_MODELS = [ "o1-preview", "o1-mini", "o3-mini", -] - -# any model starting with gpt-4- is assumed to support 'json_object' -# for others we need to explicitly state the model name -JSON_OBJECT_RESPONSE_MODELS = [ - "gpt-4o-2024-08-06", - "gpt-4o-2024-11-20", - "gpt-4o-realtime-preview", - "gpt-4o-mini-realtime-preview", - "gpt-4o", - "gpt-4o-mini", - "gpt-3.5-turbo-0125", + "gpt-5", + "gpt-5-mini", + "gpt-5-nano", ] def num_tokens_from_messages(messages: list[dict], model: str = "gpt-3.5-turbo-0613"): + # TODO this whole function probably needs to be rewritten at this point + """Return the number of tokens used by a list of messages.""" try: encoding = tiktoken.encoding_for_model(model) @@ -83,7 +75,7 @@ def num_tokens_from_messages(messages: list[dict], model: str = "gpt-3.5-turbo-0 tokens_per_name = -1 # if there's a name, the role is omitted elif "gpt-3.5-turbo" in model: return num_tokens_from_messages(messages, model="gpt-3.5-turbo-0613") - elif "gpt-4" in model or "o1" in model or "o3" in model: + elif "gpt-4" in model or "o1" in model or "o3" in model or "gpt-5" in model: return num_tokens_from_messages(messages, model="gpt-4-0613") else: raise NotImplementedError( @@ -104,9 +96,13 @@ def num_tokens_from_messages(messages: list[dict], model: str = "gpt-3.5-turbo-0 return num_tokens +DEFAULT_MODEL = "gpt-4o" + + class Defaults(EndpointOverride, CommonDefaults, pydantic.BaseModel): max_token_length: int = 16384 - model: str = "gpt-4o" + model: str = DEFAULT_MODEL + reason_tokens: int = 1024 class ClientConfig(EndpointOverride, BaseClientConfig): @@ -121,7 +117,6 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): client_type = "openai" conversation_retries = 0 - auto_break_repetition_enabled = False # TODO: make this configurable? decensor_enabled = False config_cls = ClientConfig @@ -135,18 +130,9 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): defaults: Defaults = Defaults() extra_fields: dict[str, ExtraField] = endpoint_override_extra_fields() - def __init__(self, model="gpt-4o", **kwargs): - self.model_name = model - self.api_key_status = None - self._reconfigure_endpoint_override(**kwargs) - self.config = load_config() - super().__init__(**kwargs) - - handlers["config_saved"].connect(self.on_config_saved) - @property def openai_api_key(self): - return self.config.get("openai", {}).get("api_key") + return self.config.openai.api_key @property def supported_parameters(self): @@ -157,17 +143,35 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): "max_tokens", ] + @property + def requires_reasoning_pattern(self) -> bool: + return False + def emit_status(self, processing: bool = None): error_action = None + error_message = None if processing is not None: self.processing = processing + # Auto-toggle reasoning based on selected model (OpenAI-specific) + # o1/o3/gpt-5 families are reasoning models + try: + if self.model_name: + is_reasoning_model = ( + "o1" in self.model_name + or "o3" in self.model_name + or "gpt-5" in self.model_name + ) + if self.client_config.reason_enabled != is_reasoning_model: + self.client_config.reason_enabled = is_reasoning_model + except Exception: + pass + if self.openai_api_key: status = "busy" if self.processing else "idle" - model_name = self.model_name else: status = "error" - model_name = "No API key set" + error_message = "No API key set" error_action = ErrorAction( title="Set API Key", action_name="openAppConfig", @@ -180,7 +184,7 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): if not self.model_name: status = "error" - model_name = "No model loaded" + error_message = "No model loaded" self.current_status = status @@ -188,6 +192,7 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): "error_action": error_action.model_dump() if error_action else None, "meta": self.Meta().model_dump(), "enabled": self.enabled, + "error_message": error_message, } data.update(self._common_status_data()) @@ -195,74 +200,11 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status if self.enabled else "disabled", data=data, ) - def set_client(self, max_token_length: int = None): - if not self.openai_api_key and not self.endpoint_override_base_url_configured: - self.client = AsyncOpenAI(api_key="sk-1111") - log.error("No OpenAI API key set") - if self.api_key_status: - self.api_key_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = "gpt-3.5-turbo-16k" - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - - model = self.model_name - - self.client = AsyncOpenAI(api_key=self.api_key, base_url=self.base_url) - if model == "gpt-3.5-turbo": - self.max_token_length = min(max_token_length or 4096, 4096) - elif model == "gpt-4": - self.max_token_length = min(max_token_length or 8192, 8192) - elif model == "gpt-3.5-turbo-16k": - self.max_token_length = min(max_token_length or 16384, 16384) - elif model.startswith("gpt-4o") and model != "gpt-4o-2024-05-13": - self.max_token_length = min(max_token_length or 16384, 16384) - elif model == "gpt-4o-2024-05-13": - self.max_token_length = min(max_token_length or 4096, 4096) - elif model == "gpt-4-1106-preview": - self.max_token_length = min(max_token_length or 128000, 128000) - else: - self.max_token_length = max_token_length or 8192 - - if not self.api_key_status: - if self.api_key_status is False: - emit("request_client_status") - emit("request_agent_status") - self.api_key_status = True - - log.info( - "openai set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=model, - ) - - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - self._reconfigure_common_parameters(**kwargs) - self._reconfigure_endpoint_override(**kwargs) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - def count_tokens(self, content: str): if not self.model_name: return 0 @@ -271,18 +213,6 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): async def status(self): self.emit_status() - def prompt_template(self, system_message: str, prompt: str): - # only gpt-4-1106-preview supports json_object response coersion - - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - - return prompt - async def generate(self, prompt: str, parameters: dict, kind: str): """ Generates text from the given prompt and parameters. @@ -291,26 +221,17 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): if not self.openai_api_key and not self.endpoint_override_base_url_configured: raise Exception("No OpenAI API key set") - # only gpt-4-* supports enforcing json object - supports_json_object = ( - self.model_name.startswith("gpt-4-") - or self.model_name in JSON_OBJECT_RESPONSE_MODELS - ) - right = None - expected_response = None - try: - _, right = prompt.split("\nStart your response with: ") - expected_response = right.strip() - if expected_response.startswith("{") and supports_json_object: - parameters["response_format"] = {"type": "json_object"} - except (IndexError, ValueError): - pass + client = AsyncOpenAI(api_key=self.api_key, base_url=self.base_url) human_message = {"role": "user", "content": prompt.strip()} system_message = {"role": "system", "content": self.get_system_message(kind)} # o1 and o3 models don't support system_message - if "o1" in self.model_name or "o3" in self.model_name: + if ( + "o1" in self.model_name + or "o3" in self.model_name + or "gpt-5" in self.model_name + ): messages = [human_message] # paramters need to be munged # `max_tokens` becomes `max_completion_tokens` @@ -339,13 +260,20 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): self.log.debug( "generate", + model=self.model_name, prompt=prompt[:128] + " ...", parameters=parameters, system_message=system_message, ) + # GPT-5 models do not allow streaming for non-verified orgs; use non-streaming path + if "gpt-5" in self.model_name: + return await self._generate_non_streaming_completion( + client, messages, parameters + ) + try: - stream = await self.client.chat.completions.create( + stream = await client.chat.completions.create( model=self.model_name, messages=messages, stream=True, @@ -365,23 +293,6 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): # Incrementally track token usage self.update_request_tokens(self.count_tokens(content_piece)) - # self._returned_prompt_tokens = self.prompt_tokens(prompt) - # self._returned_response_tokens = self.response_tokens(response) - - # older models don't support json_object response coersion - # and often like to return the response wrapped in ```json - # so we strip that out if the expected response is a json object - if ( - not supports_json_object - and expected_response - and expected_response.startswith("{") - ): - if response.startswith("```json") and response.endswith("```"): - response = response[7:-3].strip() - - if right and response.startswith(right): - response = response[len(right) :].strip() - return response except PermissionDeniedError as e: self.log.error("generate error", e=e) @@ -389,3 +300,36 @@ class OpenAIClient(EndpointOverrideMixin, ClientBase): return "" except Exception: raise + + async def _generate_non_streaming_completion( + self, client: AsyncOpenAI, messages: list[dict], parameters: dict + ) -> str: + """Perform a non-streaming chat completion request and return the content. + + This is used for GPT-5 models which disallow streaming for non-verified orgs. + """ + try: + response = await client.chat.completions.create( + model=self.model_name, + messages=messages, + # No stream flag -> non-streaming + **parameters, + ) + + if not response.choices: + return "" + + message = response.choices[0].message + content = getattr(message, "content", "") or "" + + if content: + # Update token usage based on the full content + self.update_request_tokens(self.count_tokens(content)) + + return content + except PermissionDeniedError as e: + self.log.error("generate (non-streaming) error", e=e) + emit("status", message="OpenAI API: Permission Denied", status="error") + return "" + except Exception: + raise diff --git a/src/talemate/client/openai_compat.py b/src/talemate/client/openai_compat.py index d86a0a88..abe5c629 100644 --- a/src/talemate/client/openai_compat.py +++ b/src/talemate/client/openai_compat.py @@ -6,7 +6,7 @@ from openai import AsyncOpenAI, PermissionDeniedError from talemate.client.base import ClientBase, ExtraField from talemate.client.registry import register -from talemate.config import Client as BaseClientConfig +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit log = structlog.get_logger("talemate.client.openai_compat") @@ -51,13 +51,9 @@ class OpenAICompatibleClient(ClientBase): ) } - def __init__( - self, model=None, api_key=None, api_handles_prompt_template=False, **kwargs - ): - self.model_name = model - self.api_key = api_key - self.api_handles_prompt_template = api_handles_prompt_template - super().__init__(**kwargs) + @property + def api_handles_prompt_template(self) -> bool: + return self.client_config.api_handles_prompt_template @property def experimental(self): @@ -69,7 +65,7 @@ class OpenAICompatibleClient(ClientBase): Determines whether or not his client can pass LLM coercion. (e.g., is able to predefine partial LLM output in the prompt) """ - return not self.api_handles_prompt_template + return not self.reason_enabled @property def supported_parameters(self): @@ -80,43 +76,21 @@ class OpenAICompatibleClient(ClientBase): "max_tokens", ] - def set_client(self, **kwargs): - self.api_key = kwargs.get("api_key", self.api_key) - self.api_handles_prompt_template = kwargs.get( - "api_handles_prompt_template", self.api_handles_prompt_template - ) - url = self.api_url - self.client = AsyncOpenAI(base_url=url, api_key=self.api_key) - self.model_name = ( - kwargs.get("model") or kwargs.get("model_name") or self.model_name - ) - def prompt_template(self, system_message: str, prompt: str): - log.debug( - "IS API HANDLING PROMPT TEMPLATE", - api_handles_prompt_template=self.api_handles_prompt_template, - ) - if not self.api_handles_prompt_template: return super().prompt_template(system_message, prompt) - - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - return prompt async def get_model_name(self): - return self.model_name + return self.model async def generate(self, prompt: str, parameters: dict, kind: str): """ Generates text from the given prompt and parameters. """ + client = AsyncOpenAI(base_url=self.api_url, api_key=self.api_key) + try: if self.api_handles_prompt_template: # OpenAI API handles prompt template @@ -126,15 +100,37 @@ class OpenAICompatibleClient(ClientBase): prompt=prompt[:128] + " ...", parameters=parameters, ) - human_message = {"role": "user", "content": prompt.strip()} - response = await self.client.chat.completions.create( + + if self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + else: + coercion_prompt = None + + messages = [ + {"role": "system", "content": self.get_system_message(kind)}, + {"role": "user", "content": prompt.strip()}, + ] + + if coercion_prompt: + log.debug( + "Adding coercion pre-fill", coercion_prompt=coercion_prompt + ) + messages.append( + { + "role": "assistant", + "content": coercion_prompt.strip(), + "prefix": True, + } + ) + + response = await client.chat.completions.create( model=self.model_name, - messages=[human_message], + messages=messages, stream=False, **parameters, ) response = response.choices[0].message.content - return self.process_response_for_indirect_coercion(prompt, response) + return response else: # Talemate handles prompt template # Use the completions endpoint @@ -144,7 +140,7 @@ class OpenAICompatibleClient(ClientBase): parameters=parameters, ) parameters["prompt"] = prompt - response = await self.client.completions.create( + response = await client.completions.create( model=self.model_name, stream=False, **parameters ) return response.choices[0].text @@ -159,34 +155,6 @@ class OpenAICompatibleClient(ClientBase): ) return "" - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - if "api_url" in kwargs: - self.api_url = kwargs["api_url"] - if "max_token_length" in kwargs: - self.max_token_length = ( - int(kwargs["max_token_length"]) if kwargs["max_token_length"] else 8192 - ) - if "api_key" in kwargs: - self.api_key = kwargs["api_key"] - if "api_handles_prompt_template" in kwargs: - self.api_handles_prompt_template = kwargs["api_handles_prompt_template"] - # TODO: why isn't this calling super()? - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - if "double_coercion" in kwargs: - self.double_coercion = kwargs["double_coercion"] - - if "rate_limit" in kwargs: - self.rate_limit = kwargs["rate_limit"] - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - self.set_client(**kwargs) - def jiggle_randomness(self, prompt_config: dict, offset: float = 0.3) -> dict: """ adjusts temperature and presence penalty diff --git a/src/talemate/client/openrouter.py b/src/talemate/client/openrouter.py index 1878630f..0a125fa5 100644 --- a/src/talemate/client/openrouter.py +++ b/src/talemate/client/openrouter.py @@ -4,9 +4,17 @@ import httpx import asyncio import json -from talemate.client.base import ClientBase, ErrorAction, CommonDefaults +from talemate.client.base import ( + ClientBase, + ErrorAction, + CommonDefaults, + ExtraField, + FieldGroup, +) +from talemate.config.schema import Client as BaseClientConfig +from talemate.config import get_config + from talemate.client.registry import register -from talemate.config import load_config from talemate.emit import emit from talemate.emit.signals import handlers @@ -18,6 +26,91 @@ log = structlog.get_logger("talemate.client.openrouter") # Available models will be populated when first client with API key is initialized AVAILABLE_MODELS = [] + +# Static list of providers that are supported by OpenRouter +# https://openrouter.ai/docs/features/provider-routing#json-schema-for-provider-preferences + + +AVAILABLE_PROVIDERS = [ + "AnyScale", + "Cent-ML", + "HuggingFace", + "Hyperbolic 2", + "Lepton", + "Lynn 2", + "Lynn", + "Mancer", + "Modal", + "OctoAI", + "Recursal", + "Reflection", + "Replicate", + "SambaNova 2", + "SF Compute", + "Together 2", + "01.AI", + "AI21", + "AionLabs", + "Alibaba", + "Amazon Bedrock", + "Anthropic", + "AtlasCloud", + "Atoma", + "Avian", + "Azure", + "BaseTen", + "Cerebras", + "Chutes", + "Cloudflare", + "Cohere", + "CrofAI", + "Crusoe", + "DeepInfra", + "DeepSeek", + "Enfer", + "Featherless", + "Fireworks", + "Friendli", + "GMICloud", + "Google", + "Google AI Studio", + "Groq", + "Hyperbolic", + "Inception", + "InferenceNet", + "Infermatic", + "Inflection", + "InoCloud", + "Kluster", + "Lambda", + "Liquid", + "Mancer 2", + "Meta", + "Minimax", + "Mistral", + "Moonshot AI", + "Morph", + "NCompass", + "Nebius", + "NextBit", + "Nineteen", + "Novita", + "OpenAI", + "OpenInference", + "Parasail", + "Perplexity", + "Phala", + "SambaNova", + "Stealth", + "Switchpoint", + "Targon", + "Together", + "Ubicloud", + "Venice", + "xAI", +] +AVAILABLE_PROVIDERS.sort() + DEFAULT_MODEL = "" MODELS_FETCHED = False @@ -25,7 +118,6 @@ MODELS_FETCHED = False async def fetch_available_models(api_key: str = None): """Fetch available models from OpenRouter API""" global AVAILABLE_MODELS, DEFAULT_MODEL, MODELS_FETCHED - if not api_key: return [] @@ -37,6 +129,7 @@ async def fetch_available_models(api_key: str = None): return AVAILABLE_MODELS try: + log.debug("Fetching models from OpenRouter") async with httpx.AsyncClient() as client: response = await client.get( "https://openrouter.ai/api/v1/models", timeout=10.0 @@ -61,19 +154,36 @@ async def fetch_available_models(api_key: str = None): return AVAILABLE_MODELS -def fetch_models_sync(event): - api_key = event.data.get("openrouter", {}).get("api_key") +def fetch_models_sync(api_key: str): loop = asyncio.get_event_loop() loop.run_until_complete(fetch_available_models(api_key)) -handlers["config_saved"].connect(fetch_models_sync) -handlers["talemate_started"].connect(fetch_models_sync) +def on_talemate_started(event): + fetch_models_sync(get_config().openrouter.api_key) + + +handlers["talemate_started"].connect(on_talemate_started) class Defaults(CommonDefaults, pydantic.BaseModel): max_token_length: int = 16384 model: str = DEFAULT_MODEL + provider_only: list[str] = pydantic.Field(default_factory=list) + provider_ignore: list[str] = pydantic.Field(default_factory=list) + + +class ClientConfig(BaseClientConfig): + provider_only: list[str] = pydantic.Field(default_factory=list) + provider_ignore: list[str] = pydantic.Field(default_factory=list) + + +PROVIDER_FIELD_GROUP = FieldGroup( + name="provider", + label="Provider", + description="Configure OpenRouter provider routing.", + icon="mdi-server-network", +) @register() @@ -84,9 +194,9 @@ class OpenRouterClient(ClientBase): client_type = "openrouter" conversation_retries = 0 - auto_break_repetition_enabled = False # TODO: make this configurable? decensor_enabled = False + config_cls = ClientConfig class Meta(ClientBase.Meta): name_prefix: str = "OpenRouter" @@ -97,23 +207,46 @@ class OpenRouterClient(ClientBase): ) requires_prompt_template: bool = False defaults: Defaults = Defaults() + extra_fields: dict[str, ExtraField] = { + "provider_only": ExtraField( + name="provider_only", + type="flags", + label="Only use these providers", + choices=AVAILABLE_PROVIDERS, + description="Manually limit the providers to use for the selected model. This will override the default provider selection for this model.", + group=PROVIDER_FIELD_GROUP, + required=False, + ), + "provider_ignore": ExtraField( + name="provider_ignore", + type="flags", + label="Ignore these providers", + choices=AVAILABLE_PROVIDERS, + description="Ignore these providers for the selected model. This will override the default provider selection for this model.", + group=PROVIDER_FIELD_GROUP, + required=False, + ), + } - def __init__(self, model=None, **kwargs): - self.model_name = model or DEFAULT_MODEL - self.api_key_status = None - self.config = load_config() + def __init__(self, **kwargs): self._models_fetched = False super().__init__(**kwargs) - handlers["config_saved"].connect(self.on_config_saved) + @property + def provider_only(self) -> list[str]: + return self.client_config.provider_only + + @property + def provider_ignore(self) -> list[str]: + return self.client_config.provider_ignore @property def can_be_coerced(self) -> bool: - return True + return not self.reason_enabled @property def openrouter_api_key(self): - return self.config.get("openrouter", {}).get("api_key") + return self.config.openrouter.api_key @property def supported_parameters(self): @@ -130,15 +263,15 @@ class OpenRouterClient(ClientBase): def emit_status(self, processing: bool = None): error_action = None + error_message = None if processing is not None: self.processing = processing if self.openrouter_api_key: status = "busy" if self.processing else "idle" - model_name = self.model_name else: status = "error" - model_name = "No API key set" + error_message = "No API key set" error_action = ErrorAction( title="Set API Key", action_name="openAppConfig", @@ -151,7 +284,7 @@ class OpenRouterClient(ClientBase): if not self.model_name: status = "error" - model_name = "No model loaded" + error_message = "No model loaded" self.current_status = status @@ -159,6 +292,7 @@ class OpenRouterClient(ClientBase): "error_action": error_action.model_dump() if error_action else None, "meta": self.Meta().model_dump(), "enabled": self.enabled, + "error_message": error_message, } data.update(self._common_status_data()) @@ -166,60 +300,11 @@ class OpenRouterClient(ClientBase): "client_status", message=self.client_type, id=self.name, - details=model_name, + details=self.model_name, status=status if self.enabled else "disabled", data=data, ) - def set_client(self, max_token_length: int = None): - # Unlike other clients, we don't need to set up a client instance - # We'll use httpx directly in the generate method - - if not self.openrouter_api_key: - log.error("No OpenRouter API key set") - if self.api_key_status: - self.api_key_status = False - emit("request_client_status") - emit("request_agent_status") - return - - if not self.model_name: - self.model_name = DEFAULT_MODEL - - if max_token_length and not isinstance(max_token_length, int): - max_token_length = int(max_token_length) - - # Set max token length (default to 16k if not specified) - self.max_token_length = max_token_length or 16384 - - if not self.api_key_status: - if self.api_key_status is False: - emit("request_client_status") - emit("request_agent_status") - self.api_key_status = True - - log.info( - "openrouter set client", - max_token_length=self.max_token_length, - provided_max_token_length=max_token_length, - model=self.model_name, - ) - - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - self._reconfigure_common_parameters(**kwargs) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - async def status(self): # Fetch models if we have an API key and haven't fetched yet if self.openrouter_api_key and not self._models_fetched: @@ -229,13 +314,6 @@ class OpenRouterClient(ClientBase): self.emit_status() - def prompt_template(self, system_message: str, prompt: str): - """ - Open-router handles the prompt template internally, so we just - give the prompt as is. - """ - return prompt - async def generate(self, prompt: str, parameters: dict, kind: str): """ Generates text from the given prompt and parameters using OpenRouter API. @@ -244,7 +322,10 @@ class OpenRouterClient(ClientBase): if not self.openrouter_api_key: raise Exception("No OpenRouter API key set") - prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + if self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + else: + coercion_prompt = None # Prepare messages for chat completion messages = [ @@ -253,7 +334,23 @@ class OpenRouterClient(ClientBase): ] if coercion_prompt: - messages.append({"role": "assistant", "content": coercion_prompt.strip()}) + log.debug("Adding coercion pre-fill", coercion_prompt=coercion_prompt) + messages.append( + { + "role": "assistant", + "content": coercion_prompt.strip(), + "prefix": True, + } + ) + + provider = {} + if self.provider_only: + provider["only"] = self.provider_only + if self.provider_ignore: + provider["ignore"] = self.provider_ignore + + if provider: + parameters["provider"] = provider # Prepare request payload payload = { @@ -320,7 +417,7 @@ class OpenRouterClient(ClientBase): self.count_tokens(content) ) - except json.JSONDecodeError: + except (json.JSONDecodeError, KeyError): pass # Extract the response content diff --git a/src/talemate/client/presets.py b/src/talemate/client/presets.py index 56d5b807..50fb85de 100644 --- a/src/talemate/client/presets.py +++ b/src/talemate/client/presets.py @@ -3,8 +3,7 @@ from typing import TYPE_CHECKING import structlog from talemate.client.context import set_client_context_attribute -from talemate.config import InferencePresets, InferencePresetGroup, load_config -from talemate.emit.signals import handlers +from talemate.config import get_config if TYPE_CHECKING: from talemate.client.base import ClientBase @@ -20,42 +19,19 @@ __all__ = [ log = structlog.get_logger("talemate.client.presets") -config = load_config(as_model=True) - - -# Load the config -CONFIG = { - "inference": config.presets.inference, - "inference_groups": config.presets.inference_groups, -} - - -# Sync the config when it is saved -def sync_config(event): - CONFIG["inference"] = InferencePresets( - **event.data.get("presets", {}).get("inference", {}) - ) - CONFIG["inference_groups"] = { - group: InferencePresetGroup(**data) - for group, data in event.data.get("presets", {}) - .get("inference_groups", {}) - .items() - } - - -handlers["config_saved"].connect(sync_config) - def get_inference_parameters(preset_name: str, group: str | None = None) -> dict: """ Returns the inference parameters for the given preset name. """ - presets = CONFIG["inference"].model_dump() + config = get_config() + + presets = config.presets.inference.model_dump() if group: try: - group_presets = CONFIG["inference_groups"].get(group).model_dump() + group_presets = config.presets.inference_groups.get(group).model_dump() presets.update(group_presets["presets"]) except AttributeError: log.warning( @@ -74,6 +50,7 @@ def configure(parameters: dict, kind: str, total_budget: int, client: "ClientBas """ set_preset(parameters, kind, client) set_max_tokens(parameters, kind, total_budget) + return parameters @@ -141,7 +118,7 @@ def preset_for_kind(kind: str, client: "ClientBase") -> dict: if not preset_name: log.warning( f"No preset found for kind {kind}, defaulting to 'scene_direction'", - presets=CONFIG["inference"], + presets=get_config().presets.inference, ) preset_name = "scene_direction" diff --git a/src/talemate/client/remote.py b/src/talemate/client/remote.py index ba9ff2ca..138cfe85 100644 --- a/src/talemate/client/remote.py +++ b/src/talemate/client/remote.py @@ -69,17 +69,13 @@ class EndpointOverrideAPIKeyField(EndpointOverrideField): class EndpointOverrideMixin: - override_base_url: str | None = None - override_api_key: str | None = None + @property + def override_base_url(self) -> str | None: + return self.client_config.override_base_url - def set_client_api_key(self, api_key: str | None): - if getattr(self, "client", None): - try: - self.client.api_key = api_key - except Exception as e: - log.error( - "Error setting client API key", error=e, client=self.client_type - ) + @property + def override_api_key(self) -> str | None: + return self.client_config.override_api_key @property def api_key(self) -> str | None: @@ -108,41 +104,7 @@ class EndpointOverrideMixin: and self.endpoint_override_api_key_configured ) - def _reconfigure_endpoint_override(self, **kwargs): - if "override_base_url" in kwargs: - orig = getattr(self, "override_base_url", None) - self.override_base_url = kwargs["override_base_url"] - if getattr(self, "client", None) and orig != self.override_base_url: - log.info("Reconfiguring client base URL", new=self.override_base_url) - self.set_client(kwargs.get("max_token_length")) - - if "override_api_key" in kwargs: - self.override_api_key = kwargs["override_api_key"] - self.set_client_api_key(self.override_api_key) - class RemoteServiceMixin: - def prompt_template(self, system_message: str, prompt: str): - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - - return prompt - - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - self.set_client(kwargs.get("max_token_length")) - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - - def on_config_saved(self, event): - config = event.data - self.config = config - self.set_client(max_token_length=self.max_token_length) - async def status(self): self.emit_status() diff --git a/src/talemate/client/runpod.py b/src/talemate/client/runpod.py index b3109f8e..f2bb8ad9 100644 --- a/src/talemate/client/runpod.py +++ b/src/talemate/client/runpod.py @@ -9,7 +9,7 @@ import dotenv import runpod import structlog -from talemate.config import load_config +from talemate.config import get_config from .bootstrap import ClientBootstrap, ClientType, register_list @@ -17,7 +17,6 @@ log = structlog.get_logger("talemate.client.runpod") dotenv.load_dotenv() -runpod.api_key = load_config().get("runpod", {}).get("api_key", "") TEXTGEN_IDENTIFIERS = ["textgen", "thebloke llms", "text-generation-webui"] @@ -35,6 +34,7 @@ async def _async_get_pods(): """ asyncio wrapper around get_pods. """ + runpod.api_key = get_config().runpod.api_key loop = asyncio.get_event_loop() return await loop.run_in_executor(None, runpod.get_pods) @@ -44,6 +44,7 @@ async def get_textgen_pods(): """ Return a list of text generation pods. """ + runpod.api_key = get_config().runpod.api_key if not runpod.api_key: return @@ -60,6 +61,8 @@ async def get_automatic1111_pods(): Return a list of automatic1111 pods. """ + runpod.api_key = get_config().runpod.api_key + if not runpod.api_key: return diff --git a/src/talemate/client/tabbyapi.py b/src/talemate/client/tabbyapi.py index 65bc910e..58e8ccab 100644 --- a/src/talemate/client/tabbyapi.py +++ b/src/talemate/client/tabbyapi.py @@ -8,7 +8,7 @@ from openai import PermissionDeniedError from talemate.client.base import ClientBase, ExtraField, CommonDefaults from talemate.client.registry import register from talemate.client.utils import urljoin -from talemate.config import Client as BaseClientConfig +from talemate.config.schema import Client as BaseClientConfig from talemate.emit import emit log = structlog.get_logger("talemate.client.tabbyapi") @@ -34,6 +34,7 @@ class TabbyAPIClient(ClientBase): client_type = "tabbyapi" conversation_retries = 0 config_cls = ClientConfig + remote_model_locked: bool = True class Meta(ClientBase.Meta): title: str = "TabbyAPI" @@ -52,13 +53,9 @@ class TabbyAPIClient(ClientBase): ) } - def __init__( - self, model=None, api_key=None, api_handles_prompt_template=False, **kwargs - ): - self.model_name = model - self.api_key = api_key - self.api_handles_prompt_template = api_handles_prompt_template - super().__init__(**kwargs) + @property + def api_handles_prompt_template(self) -> bool: + return self.client_config.api_handles_prompt_template @property def experimental(self): @@ -69,7 +66,7 @@ class TabbyAPIClient(ClientBase): """ Determines whether or not this client can pass LLM coercion. (e.g., is able to predefine partial LLM output in the prompt) """ - return not self.api_handles_prompt_template + return not self.reason_enabled @property def supported_parameters(self): @@ -92,31 +89,9 @@ class TabbyAPIClient(ClientBase): "temperature", ] - def set_client(self, **kwargs): - self.api_key = kwargs.get("api_key", self.api_key) - self.api_handles_prompt_template = kwargs.get( - "api_handles_prompt_template", self.api_handles_prompt_template - ) - self.model_name = ( - kwargs.get("model") or kwargs.get("model_name") or self.model_name - ) - def prompt_template(self, system_message: str, prompt: str): - log.debug( - "IS API HANDLING PROMPT TEMPLATE", - api_handles_prompt_template=self.api_handles_prompt_template, - ) - if not self.api_handles_prompt_template: return super().prompt_template(system_message, prompt) - - if "<|BOT|>" in prompt: - _, right = prompt.split("<|BOT|>", 1) - if right: - prompt = prompt.replace("<|BOT|>", "\nStart your response with: ") - else: - prompt = prompt.replace("<|BOT|>", "") - return prompt async def get_model_name(self): @@ -152,11 +127,31 @@ class TabbyAPIClient(ClientBase): parameters=parameters, ) - human_message = {"role": "user", "content": prompt.strip()} + if self.can_be_coerced: + prompt, coercion_prompt = self.split_prompt_for_coercion(prompt) + else: + coercion_prompt = None + + messages = [ + {"role": "system", "content": self.get_system_message(kind)}, + {"role": "user", "content": prompt.strip()}, + ] + + if coercion_prompt: + log.debug( + "Adding coercion pre-fill", coercion_prompt=coercion_prompt + ) + messages.append( + { + "role": "assistant", + "content": coercion_prompt.strip(), + "prefix": True, + } + ) payload = { "model": self.model_name, - "messages": [human_message], + "messages": messages, "stream": True, "stream_options": { "include_usage": True, @@ -229,6 +224,10 @@ class TabbyAPIClient(ClientBase): ) usage = data_obj.get("usage", {}) + + if not usage: + continue + completion_tokens = usage.get( "completion_tokens", 0 ) @@ -239,7 +238,7 @@ class TabbyAPIClient(ClientBase): self.update_request_tokens( self.count_tokens(content) ) - except json.JSONDecodeError: + except (json.JSONDecodeError, IndexError): # ignore malformed json chunks pass @@ -247,12 +246,6 @@ class TabbyAPIClient(ClientBase): self._returned_prompt_tokens = prompt_tokens self._returned_response_tokens = completion_tokens - if is_chat: - # Process indirect coercion - response_text = self.process_response_for_indirect_coercion( - prompt, response_text - ) - return response_text except PermissionDeniedError as e: @@ -264,34 +257,15 @@ class TabbyAPIClient(ClientBase): emit("status", message="TabbyAPI: Request timed out", status="error") return "" except Exception as e: + import traceback + + print(traceback.format_exc()) self.log.error("generate error", e=e) emit( "status", message="Error during generation (check logs)", status="error" ) return "" - def reconfigure(self, **kwargs): - if kwargs.get("model"): - self.model_name = kwargs["model"] - if "api_url" in kwargs: - self.api_url = kwargs["api_url"] - if "max_token_length" in kwargs: - self.max_token_length = ( - int(kwargs["max_token_length"]) if kwargs["max_token_length"] else 8192 - ) - if "api_key" in kwargs: - self.api_key = kwargs["api_key"] - if "api_handles_prompt_template" in kwargs: - self.api_handles_prompt_template = kwargs["api_handles_prompt_template"] - if "enabled" in kwargs: - self.enabled = bool(kwargs["enabled"]) - if "double_coercion" in kwargs: - self.double_coercion = kwargs["double_coercion"] - - self._reconfigure_common_parameters(**kwargs) - - self.set_client(**kwargs) - def jiggle_randomness(self, prompt_config: dict, offset: float = 0.3) -> dict: """ adjusts temperature and presence penalty by random values using the base value as a center diff --git a/src/talemate/client/textgenwebui.py b/src/talemate/client/textgenwebui.py index 94c0a053..f25a74ac 100644 --- a/src/talemate/client/textgenwebui.py +++ b/src/talemate/client/textgenwebui.py @@ -6,7 +6,6 @@ import requests import asyncio import httpx import structlog -from openai import AsyncOpenAI from talemate.client.base import STOPPING_STRINGS, ClientBase, Defaults from talemate.client.registry import register @@ -21,6 +20,7 @@ class TextGeneratorWebuiClientDefaults(Defaults): @register() class TextGeneratorWebuiClient(ClientBase): auto_determine_prompt_template: bool = True + remote_model_locked: bool = True finalizers: list[str] = [ "finalize_llama3", "finalize_YI", @@ -81,10 +81,6 @@ class TextGeneratorWebuiClient(ClientBase): "extra_stopping_strings", ] - def __init__(self, **kwargs): - self.api_key = kwargs.pop("api_key", "") - super().__init__(**kwargs) - def tune_prompt_parameters(self, parameters: dict, kind: str): super().tune_prompt_parameters(parameters, kind) parameters["stopping_strings"] = STOPPING_STRINGS + parameters.get( @@ -98,10 +94,6 @@ class TextGeneratorWebuiClient(ClientBase): if parameters.get("min_p"): parameters["do_sample"] = True - def set_client(self, **kwargs): - self.api_key = kwargs.get("api_key", self.api_key) - self.client = AsyncOpenAI(base_url=self.api_url + "/v1", api_key="sk-1111") - def finalize_llama3(self, parameters: dict, prompt: str) -> tuple[str, bool]: if "<|eot_id|>" not in prompt: return prompt, False @@ -213,9 +205,3 @@ class TextGeneratorWebuiClient(ClientBase): prompt_config["repetition_penalty"] = random.uniform( rep_pen + min_offset * 0.3, rep_pen + offset * 0.3 ) - - def reconfigure(self, **kwargs): - if "api_key" in kwargs: - self.api_key = kwargs.pop("api_key") - - super().reconfigure(**kwargs) diff --git a/src/talemate/commands/__init__.py b/src/talemate/commands/__init__.py index 4465f439..1ca3ccc8 100644 --- a/src/talemate/commands/__init__.py +++ b/src/talemate/commands/__init__.py @@ -10,8 +10,6 @@ from .cmd_rebuild_archive import CmdRebuildArchive # noqa: F401 from .cmd_rename import CmdRename # noqa: F401 from .cmd_regenerate import CmdRegenerate # noqa: F401 from .cmd_reset import CmdReset # noqa: F401 -from .cmd_save import CmdSave # noqa: F401 -from .cmd_save_as import CmdSaveAs # noqa: F401 from .cmd_setenv import CmdSetEnvironmentToCreative, CmdSetEnvironmentToScene # noqa: F401 from .cmd_time_util import CmdAdvanceTime # noqa: F401 from .cmd_tts import CmdTestTTS # noqa: F401 diff --git a/src/talemate/commands/cmd_debug_tools.py b/src/talemate/commands/cmd_debug_tools.py index 1853e1cb..ea88adb8 100644 --- a/src/talemate/commands/cmd_debug_tools.py +++ b/src/talemate/commands/cmd_debug_tools.py @@ -51,7 +51,7 @@ class CmdLongTermMemoryStats(TalemateCommand): aliases = ["ltm_stats"] async def run(self): - memory = self.scene.get_helper("memory").agent + memory = get_agent("memory") count = await memory.count() db_name = memory.db_name diff --git a/src/talemate/commands/cmd_rebuild_archive.py b/src/talemate/commands/cmd_rebuild_archive.py index 0182c16f..be1f894b 100644 --- a/src/talemate/commands/cmd_rebuild_archive.py +++ b/src/talemate/commands/cmd_rebuild_archive.py @@ -1,6 +1,7 @@ from talemate.commands.base import TalemateCommand from talemate.commands.manager import register from talemate.emit import emit +from talemate.instance import get_agent @register @@ -14,8 +15,8 @@ class CmdRebuildArchive(TalemateCommand): aliases = ["rebuild"] async def run(self): - summarizer = self.scene.get_helper("summarizer") - memory = self.scene.get_helper("memory") + summarizer = get_agent("summarizer") + memory = get_agent("memory") if not summarizer: self.system_message("No summarizer found") diff --git a/src/talemate/commands/cmd_save.py b/src/talemate/commands/cmd_save.py deleted file mode 100644 index bb82cee5..00000000 --- a/src/talemate/commands/cmd_save.py +++ /dev/null @@ -1,17 +0,0 @@ -from talemate.commands.base import TalemateCommand -from talemate.commands.manager import register - - -@register -class CmdSave(TalemateCommand): - """ - Command class for the 'save' command - """ - - name = "save" - description = "Save the scene" - aliases = ["s"] - sets_scene_unsaved = False - - async def run(self): - await self.scene.save() diff --git a/src/talemate/commands/cmd_save_as.py b/src/talemate/commands/cmd_save_as.py deleted file mode 100644 index 53c7479d..00000000 --- a/src/talemate/commands/cmd_save_as.py +++ /dev/null @@ -1,17 +0,0 @@ -from talemate.commands.base import TalemateCommand -from talemate.commands.manager import register - - -@register -class CmdSaveAs(TalemateCommand): - """ - Command class for the 'save_as' command - """ - - name = "save_as" - description = "Save the scene with a new name" - aliases = ["sa"] - sets_scene_unsaved = False - - async def run(self): - await self.scene.save(save_as=True) diff --git a/src/talemate/config/__init__.py b/src/talemate/config/__init__.py new file mode 100644 index 00000000..7b48d0f3 --- /dev/null +++ b/src/talemate/config/__init__.py @@ -0,0 +1,11 @@ +from .state import get_config, save_config, cleanup, update_config, commit_config +from .schema import Config + +__all__ = [ + "get_config", + "save_config", + "cleanup", + "Config", + "update_config", + "commit_config", +] diff --git a/src/talemate/config.py b/src/talemate/config/schema.py similarity index 67% rename from src/talemate/config.py rename to src/talemate/config/schema.py index df2db8d5..b528a92a 100644 --- a/src/talemate/config.py +++ b/src/talemate/config/schema.py @@ -4,45 +4,66 @@ from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, TypeVar, Union, import pydantic import structlog -import yaml -from pydantic import BaseModel from typing_extensions import Annotated -from talemate.agents.registry import get_agent_class +import talemate.emit.async_signals as async_signals + from talemate.client.registry import get_client_class from talemate.client.system_prompts import SystemPrompts -from talemate.emit import emit from talemate.scene_assets import Asset +from talemate.path import SCENES_DIR + if TYPE_CHECKING: from talemate.tale_mate import Scene log = structlog.get_logger("talemate.config") - -def scenes_dir(): - relative_path = os.path.join( - os.path.dirname(os.path.realpath(__file__)), - "..", - "..", - "scenes", - ) - return os.path.abspath(relative_path) +async_signals.register( + "config.changed", + "config.changed.follow", +) -class Client(BaseModel): +class Client(pydantic.BaseModel): + """ + LLM Client configuration + """ + + # clien type/provider (e.g., openai, anthropic, etc.) type: str name: str model: Union[str, None] = None api_url: Union[str, None] = None api_key: Union[str, None] = None + # max input tokens to send with a generation request max_token_length: int = 8192 + + # prefill text for ALL requests double_coercion: Union[str, None] = None + + # max requests per minute rate_limit: Union[int, None] = None + + # expected data structure format in responses data_format: Literal["json", "yaml"] | None = None + enabled: bool = True + # whether or not to enable reasoning + reason_enabled: bool = False + + # add extra allowance for response tokens + # this is useful for when the model generates visible thinking + # tokens. + reason_tokens: int = 0 + + # regex to strip from the response if the model is reasoning + reason_response_pattern: Union[str, None] = None + system_prompts: SystemPrompts = SystemPrompts() + + # inference preset group to use for this client preset_group: str | None = None class Config: @@ -52,16 +73,16 @@ class Client(BaseModel): ClientType = TypeVar("ClientType", bound=Client) -class AgentActionConfig(BaseModel): - value: Union[int, float, str, bool, list[bool | str | int | float], None] = None +class AgentActionConfig(pydantic.BaseModel): + value: Union[int, float, str, bool, list, None] = None -class AgentAction(BaseModel): +class AgentAction(pydantic.BaseModel): enabled: bool = True config: Union[dict[str, AgentActionConfig], None] = None -class Agent(BaseModel): +class Agent(pydantic.BaseModel): name: Union[str, None] = None client: Union[str, None] = None actions: Union[dict[str, AgentAction], None] = None @@ -77,7 +98,7 @@ class Agent(BaseModel): return super().model_dump(exclude_none=True) -class GamePlayerCharacter(BaseModel): +class GamePlayerCharacter(pydantic.BaseModel): name: str = "" color: str = "#3362bb" gender: str = "" @@ -87,14 +108,14 @@ class GamePlayerCharacter(BaseModel): extra = "ignore" -class General(BaseModel): +class General(pydantic.BaseModel): auto_save: bool = True auto_progress: bool = True - max_backscroll: int = 512 + max_backscroll: int = 100 add_default_character: bool = True -class StateReinforcementTemplate(BaseModel): +class StateReinforcementTemplate(pydantic.BaseModel): name: str query: str state_type: str = "npc" @@ -108,7 +129,7 @@ class StateReinforcementTemplate(BaseModel): type: ClassVar = "state_reinforcement" -class WorldStateTemplates(BaseModel): +class WorldStateTemplates(pydantic.BaseModel): state_reinforcement: dict[str, StateReinforcementTemplate] = pydantic.Field( default_factory=dict ) @@ -117,11 +138,11 @@ class WorldStateTemplates(BaseModel): return self.state_reinforcement.get(name) -class WorldState(BaseModel): +class WorldState(pydantic.BaseModel): templates: WorldStateTemplates = WorldStateTemplates() -class Game(BaseModel): +class Game(pydantic.BaseModel): default_player_character: GamePlayerCharacter = GamePlayerCharacter() general: General = General() world_state: WorldState = WorldState() @@ -130,71 +151,60 @@ class Game(BaseModel): extra = "ignore" -class CreatorConfig(BaseModel): +class CreatorConfig(pydantic.BaseModel): content_context: list[str] = [ "a fun and engaging slice of life story aimed at an adult audience." ] -class OpenAIConfig(BaseModel): +class OpenAIConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class MistralAIConfig(BaseModel): +class MistralAIConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class AnthropicConfig(BaseModel): +class AnthropicConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class CohereConfig(BaseModel): +class CohereConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class GroqConfig(BaseModel): +class GroqConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class DeepSeekConfig(BaseModel): +class DeepSeekConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class OpenRouterConfig(BaseModel): +class OpenRouterConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class RunPodConfig(BaseModel): +class RunPodConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class ElevenLabsConfig(BaseModel): +class ElevenLabsConfig(pydantic.BaseModel): api_key: Union[str, None] = None model: str = "eleven_turbo_v2" -class CoquiConfig(BaseModel): +class CoquiConfig(pydantic.BaseModel): api_key: Union[str, None] = None -class GoogleConfig(BaseModel): +class GoogleConfig(pydantic.BaseModel): gcloud_credentials_path: Union[str, None] = None gcloud_location: Union[str, None] = None api_key: Union[str, None] = None -class TTSVoiceSamples(BaseModel): - label: str - value: str - - -class TTSConfig(BaseModel): - device: str = "cuda" - model: str = "tts_models/multilingual/multi-dataset/xtts_v2" - voices: list[TTSVoiceSamples] = pydantic.Field(default_factory=list) - - -class RecentScene(BaseModel): +class RecentScene(pydantic.BaseModel): name: str path: str filename: str @@ -202,7 +212,7 @@ class RecentScene(BaseModel): cover_image: Union[Asset, None] = None -class EmbeddingFunctionPreset(BaseModel): +class EmbeddingFunctionPreset(pydantic.BaseModel): embeddings: str = "sentence-transformer" model: str = "all-MiniLM-L6-v2" trust_remote_code: bool = False @@ -263,7 +273,7 @@ def generate_chromadb_presets() -> dict[str, EmbeddingFunctionPreset]: } -class InferenceParameters(BaseModel): +class InferenceParameters(pydantic.BaseModel): temperature: float = 1.0 temperature_last: bool = True top_p: float | None = 1.0 @@ -290,7 +300,7 @@ class InferenceParameters(BaseModel): changed: bool = False -class InferencePresets(BaseModel): +class InferencePresets(pydantic.BaseModel): analytical: InferenceParameters = InferenceParameters( temperature=0.7, presence_penalty=0, @@ -324,12 +334,12 @@ class InferencePresets(BaseModel): ) -class InferencePresetGroup(BaseModel): +class InferencePresetGroup(pydantic.BaseModel): name: str presets: InferencePresets -class Presets(BaseModel): +class Presets(pydantic.BaseModel): inference_defaults: InferencePresets = InferencePresets() inference: InferencePresets = InferencePresets() @@ -353,9 +363,7 @@ def gnerate_intro_scenes(): scenes = [ RecentScene( name="Simulation Suite V2", - path=os.path.join( - scenes_dir(), "simulation-suite-v2", "the-simulation-suite.json" - ), + path=str(SCENES_DIR / "simulation-suite-v2" / "the-simulation-suite.json"), filename="the-simulation-suite.json", date=datetime.datetime.now().isoformat(), cover_image=Asset( @@ -366,7 +374,7 @@ def gnerate_intro_scenes(): ), RecentScene( name="Infinity Quest", - path=os.path.join(scenes_dir(), "infinity-quest", "infinity-quest.json"), + path=str(SCENES_DIR / "infinity-quest" / "infinity-quest.json"), filename="infinity-quest.json", date=datetime.datetime.now().isoformat(), cover_image=Asset( @@ -377,8 +385,8 @@ def gnerate_intro_scenes(): ), RecentScene( name="Infinity Quest Dynamic Story", - path=os.path.join( - scenes_dir(), "infinity-quest-dynamic-story-v2", "infinity-quest.json" + path=str( + SCENES_DIR / "infinity-quest-dynamic-story-v2" / "infinity-quest.json" ), filename="infinity-quest.json", date=datetime.datetime.now().isoformat(), @@ -393,7 +401,7 @@ def gnerate_intro_scenes(): return scenes -class RecentScenes(BaseModel): +class RecentScenes(pydantic.BaseModel): scenes: list[RecentScene] = pydantic.Field(default_factory=gnerate_intro_scenes) max_entries: int = 10 @@ -473,7 +481,7 @@ AnnotatedClient = Annotated[ ] -class HistoryMessageStyle(BaseModel): +class HistoryMessageStyle(pydantic.BaseModel): italic: bool = False bold: bool = False @@ -486,7 +494,7 @@ class HidableHistoryMessageStyle(HistoryMessageStyle): show: bool = True -class SceneAppearance(BaseModel): +class SceneAppearance(pydantic.BaseModel): narrator_messages: HistoryMessageStyle = HistoryMessageStyle(italic=True) character_messages: HistoryMessageStyle = HistoryMessageStyle() director_messages: HidableHistoryMessageStyle = HidableHistoryMessageStyle() @@ -496,11 +504,11 @@ class SceneAppearance(BaseModel): ) -class Appearance(BaseModel): +class Appearance(pydantic.BaseModel): scene: SceneAppearance = SceneAppearance() -class Config(BaseModel): +class Config(pydantic.BaseModel): clients: Dict[str, AnnotatedClient] = {} game: Game = Game() @@ -531,8 +539,6 @@ class Config(BaseModel): coqui: CoquiConfig = CoquiConfig() - tts: TTSConfig = TTSConfig() - recent_scenes: RecentScenes = RecentScenes() presets: Presets = Presets() @@ -541,146 +547,18 @@ class Config(BaseModel): system_prompts: SystemPrompts = SystemPrompts() + dirty: bool = pydantic.Field(default=False, exclude=True) + class Config: extra = "ignore" - def save(self, file_path: str = "./config.yaml"): - save_config(self, file_path) + async def set_dirty(self): + self.dirty = True + await async_signals.get("config.changed").send(self) + await async_signals.get("config.changed.follow").send(self) -class SceneAssetUpload(BaseModel): +class SceneAssetUpload(pydantic.BaseModel): scene_cover_image: bool character_cover_image: str | None = None content: str = None - - -def load_config( - file_path: str = "./config.yaml", as_model: bool = False -) -> Union[dict, Config]: - """ - Load the config file from the given path. - - Should cache the config and only reload if the file modification time - has changed since the last load - """ - with open(file_path, "r") as file: - config_data = yaml.safe_load(file) - - try: - config = Config(**config_data) - config.recent_scenes.clean() - except pydantic.ValidationError as e: - log.error("config validation", error=e) - return None - - if as_model: - return config - - return config.model_dump() - - -def save_config(config, file_path: str = "./config.yaml"): - """ - Save the config file to the given path. - """ - - log.debug("Saving config", file_path=file_path) - - # If config is a Config instance, convert it to a dictionary - if isinstance(config, Config): - config = config.model_dump(exclude_none=True) - elif isinstance(config, dict): - # validate - try: - config = Config(**config).model_dump(exclude_none=True) - except pydantic.ValidationError as e: - log.error("config validation", error=e) - return None - - # we dont want to persist the following, so we drop them: - # - presets.inference_defaults - # - presets.embeddings_defaults - - if "inference_defaults" in config["presets"]: - config["presets"].pop("inference_defaults") - - if "embeddings_defaults" in config["presets"]: - config["presets"].pop("embeddings_defaults") - - # for normal presets we only want to persist if they have changed - for preset_name, preset in list(config["presets"]["inference"].items()): - if not preset.get("changed"): - config["presets"]["inference"].pop(preset_name) - - # in inference groups also only keep if changed - for group_name, group in list(config["presets"]["inference_groups"].items()): - for preset_name, preset in list(group["presets"].items()): - if not preset.get("changed"): - group["presets"].pop(preset_name) - - # if presets is empty, remove it - if not config["presets"]["inference"]: - config["presets"].pop("inference") - - # if system_prompts is empty, remove it - if not config["system_prompts"]: - config.pop("system_prompts") - - # set any client preset_group to "" if it references an - # entry that no longer exists in inference_groups - for client in config["clients"].values(): - if not client.get("preset_group"): - continue - - if client["preset_group"] not in config["presets"].get("inference_groups", {}): - log.warning( - f"Client {client['name']} references non-existent preset group {client['preset_group']}, setting to default" - ) - client["preset_group"] = "" - - with open(file_path, "w") as file: - yaml.dump(config, file) - - emit("config_saved", data=config) - - -def cleanup() -> Config: - log.info("cleaning up config") - - config = load_config(as_model=True) - - cleanup_removed_clients(config) - cleanup_removed_agents(config) - - save_config(config) - - return config - - -def cleanup_removed_clients(config: Config): - """ - Will remove any clients that are no longer present - """ - - if not config: - return - - for client_in_config in list(config.clients.keys()): - client_config = config.clients[client_in_config] - if not get_client_class(client_config.type): - log.info("removing client from config", client=client_in_config) - del config.clients[client_in_config] - - -def cleanup_removed_agents(config: Config): - """ - Will remove any agents that are no longer present - """ - - if not config: - return - - for agent_in_config in list(config.agents.keys()): - if not get_agent_class(agent_in_config): - log.info("removing agent from config", agent=agent_in_config) - del config.agents[agent_in_config] diff --git a/src/talemate/config/state.py b/src/talemate/config/state.py new file mode 100644 index 00000000..56ff9d3e --- /dev/null +++ b/src/talemate/config/state.py @@ -0,0 +1,161 @@ +import structlog +import yaml +from talemate.path import CONFIG_FILE +import talemate.emit.async_signals as async_signals +from talemate.agents.registry import get_agent_class +from talemate.client.registry import get_client_class + +from .schema import Config + +log = structlog.get_logger("talemate.config") + +CONFIG = None + +async_signals.register( + "config.saved", + "config.saved.after", + "config.loaded", +) + + +def _load_config() -> Config: + log.debug("loading config", file_path=CONFIG_FILE) + with open(CONFIG_FILE, "r") as file: + yaml_data = yaml.safe_load(file) + return Config.model_validate(yaml_data) + + +def get_config() -> Config: + global CONFIG + if CONFIG is None: + CONFIG = _load_config() + return CONFIG + + +async def update_config(other_config: Config | dict): + if isinstance(other_config, dict): + keys = list(other_config.keys()) + other_config = Config.model_validate(other_config) + else: + keys = None + + config: Config = get_config() + + # if keys is None, do full update + if keys is None: + for field in Config.model_fields: + setattr(config, field.name, getattr(other_config, field.name)) + else: + for key in keys: + setattr(config, key, getattr(other_config, key)) + + await config.set_dirty() + + +def save_config(): + """ + Save the config file to the given path. + """ + + log.debug("Saving config", file_path=CONFIG_FILE) + + config = get_config().model_dump(exclude_none=True) + + # we dont want to persist the following, so we drop them: + # - presets.inference_defaults + # - presets.embeddings_defaults + + if "inference_defaults" in config["presets"]: + config["presets"].pop("inference_defaults") + + if "embeddings_defaults" in config["presets"]: + config["presets"].pop("embeddings_defaults") + + # for normal presets we only want to persist if they have changed + for preset_name, preset in list(config["presets"]["inference"].items()): + if not preset.get("changed"): + config["presets"]["inference"].pop(preset_name) + + # in inference groups also only keep if changed + for _, group in list(config["presets"]["inference_groups"].items()): + for preset_name, preset in list(group["presets"].items()): + if not preset.get("changed"): + group["presets"].pop(preset_name) + + # if presets is empty, remove it + if not config["presets"]["inference"]: + config["presets"].pop("inference") + + # if system_prompts is empty, remove it + if not config["system_prompts"]: + config.pop("system_prompts") + + # set any client preset_group to "" if it references an + # entry that no longer exists in inference_groups + for client in config["clients"].values(): + if not client.get("preset_group"): + continue + + if client["preset_group"] not in config["presets"].get("inference_groups", {}): + log.warning( + f"Client {client['name']} references non-existent preset group {client['preset_group']}, setting to default" + ) + client["preset_group"] = "" + + with open(CONFIG_FILE, "w") as file: + yaml.dump(config, file) + + +def cleanup_removed_clients(config: Config): + """ + Will remove any clients that are no longer present + """ + + if not config: + return + + for client_in_config in list(config.clients.keys()): + client_config = config.clients[client_in_config] + if not get_client_class(client_config.type): + log.info("removing client from config", client=client_in_config) + del config.clients[client_in_config] + + +def cleanup_removed_agents(config: Config): + """ + Will remove any agents that are no longer present + """ + + if not config: + return + + for agent_in_config in list(config.agents.keys()): + if not get_agent_class(agent_in_config): + log.info("removing agent from config", agent=agent_in_config) + del config.agents[agent_in_config] + + +def cleanup() -> Config: + log.info("cleaning up config") + + config = get_config() + + cleanup_removed_clients(config) + cleanup_removed_agents(config) + + save_config() + + return config + + +async def commit_config(): + """ + Will commit the config to the file + """ + + config = get_config() + if not config.dirty: + return + + save_config() + config.dirty = False diff --git a/src/talemate/emit/signals.py b/src/talemate/emit/signals.py index 35150312..635ab1fb 100644 --- a/src/talemate/emit/signals.py +++ b/src/talemate/emit/signals.py @@ -38,8 +38,6 @@ AudioQueue = signal("audio_queue") MessageEdited = signal("message_edited") -ConfigSaved = signal("config_saved") - ImageGenerated = signal("image_generated") ImageGenerationFailed = signal("image_generation_failed") @@ -79,7 +77,6 @@ handlers = { "message_edited": MessageEdited, "prompt_sent": PromptSent, "audio_queue": AudioQueue, - "config_saved": ConfigSaved, "status": StatusMessage, "image_generated": ImageGenerated, "image_generation_failed": ImageGenerationFailed, diff --git a/src/talemate/exceptions.py b/src/talemate/exceptions.py index adc10ef8..dd5a2555 100644 --- a/src/talemate/exceptions.py +++ b/src/talemate/exceptions.py @@ -1,3 +1,22 @@ +__all__ = [ + "TalemateError", + "TalemateInterrupt", + "ExitScene", + "RestartSceneLoop", + "ResetScene", + "GenerationCancelled", + "GenerationProcessingError", + "ReasoningResponseError", + "RenderPromptError", + "LLMAccuracyError", + "SceneInactiveError", + "UnknownDataSpec", + "ActedAsCharacter", + "AbortCommand", + "AbortWaitForInput", +] + + class TalemateError(Exception): pass @@ -42,6 +61,25 @@ class GenerationCancelled(TalemateInterrupt): pass +class GenerationProcessingError(TalemateError): + """ + Exception to raise when there is an error processing a generation + """ + + pass + + +class ReasoningResponseError(GenerationProcessingError): + """ + Exception to raise when there is an error processing a reasoning response + """ + + def __init__(self): + super().__init__( + "Reasoning response pattern not found in response - this means that either the pattern is wrong, the reasoning budget is too low or the model does not support reasoning." + ) + + class RenderPromptError(TalemateError): """ Exception to raise when there is an error rendering a prompt diff --git a/src/talemate/export.py b/src/talemate/export.py index 0157ef91..8101ea24 100644 --- a/src/talemate/export.py +++ b/src/talemate/export.py @@ -4,21 +4,32 @@ Functions that facilitate exporting of a talemate scene import base64 import enum +import os +import shutil +import tempfile +import zipfile +from pathlib import Path +from typing import Union import pydantic +import structlog from talemate.tale_mate import Scene +log = structlog.get_logger("talemate.export") + __all__ = [ "ExportFormat", "ExportOptions", "export", "export_talemate", + "export_talemate_complete", ] class ExportFormat(str, enum.Enum): talemate = "talemate" + talemate_complete = "talemate_complete" class ExportOptions(pydantic.BaseModel): @@ -29,22 +40,28 @@ class ExportOptions(pydantic.BaseModel): name: str format: ExportFormat = ExportFormat.talemate reset_progress: bool = True + include_assets: bool = True + include_nodes: bool = True + include_info: bool = True + include_templates: bool = True -async def export(scene: Scene, options: ExportOptions): +async def export(scene: Scene, options: ExportOptions) -> Union[str, bytes]: """ Export a scene """ if options.format == ExportFormat.talemate: return await export_talemate(scene, options) + elif options.format == ExportFormat.talemate_complete: + return await export_talemate_complete(scene, options) raise ValueError(f"Unsupported export format: {options.format}") async def export_talemate(scene: Scene, options: ExportOptions) -> str: """ - Export a scene in talemate format + Export a scene in talemate format (JSON only, legacy format) """ # Reset progress if options.reset_progress: @@ -52,10 +69,164 @@ async def export_talemate(scene: Scene, options: ExportOptions) -> str: # Export scene - # json strng + # json string scene_json = scene.json # encode base64 scene_base64 = base64.b64encode(scene_json.encode()).decode() return scene_base64 + + +async def export_talemate_complete(scene: Scene, options: ExportOptions) -> bytes: + """ + Export a complete scene in ZIP format including all assets, nodes, info, and templates + """ + # Reset progress + if options.reset_progress: + scene.reset() + + log.info( + "Starting complete scene export", + scene_name=scene.name, + options=options.model_dump(), + ) + + # Create temporary directory for export + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + # Export main scene JSON + scene_json_path = temp_path / "scene.json" + with open(scene_json_path, "w", encoding="utf-8") as f: + f.write(scene.json) + + log.debug("Exported scene JSON", path=scene_json_path) + + # Copy assets directory if it exists and option is enabled + if options.include_assets and scene.assets: + try: + assets_source = Path(scene.assets.asset_directory) + if assets_source.exists(): + assets_dest = temp_path / "assets" + shutil.copytree(assets_source, assets_dest) + log.debug( + "Copied assets directory", + source=assets_source, + dest=assets_dest, + ) + else: + log.debug("Assets directory does not exist", path=assets_source) + except Exception as e: + log.warning("Failed to copy assets directory", error=str(e)) + + # Copy nodes directory if it exists and option is enabled + if options.include_nodes: + try: + nodes_source = Path(scene.save_dir) / "nodes" + if nodes_source.exists(): + nodes_dest = temp_path / "nodes" + shutil.copytree(nodes_source, nodes_dest) + log.debug( + "Copied nodes directory", source=nodes_source, dest=nodes_dest + ) + else: + log.debug("Nodes directory does not exist", path=nodes_source) + except Exception as e: + log.warning("Failed to copy nodes directory", error=str(e)) + + # Copy info directory if it exists and option is enabled + if options.include_info: + try: + info_source = Path(scene.save_dir) / "info" + if info_source.exists(): + info_dest = temp_path / "info" + shutil.copytree(info_source, info_dest) + log.debug( + "Copied info directory", source=info_source, dest=info_dest + ) + else: + log.debug("Info directory does not exist", path=info_source) + except Exception as e: + log.warning("Failed to copy info directory", error=str(e)) + + # Copy templates directory if it exists and option is enabled + if options.include_templates: + try: + templates_source = Path(scene.save_dir) / "templates" + if templates_source.exists(): + templates_dest = temp_path / "templates" + shutil.copytree(templates_source, templates_dest) + log.debug( + "Copied templates directory", + source=templates_source, + dest=templates_dest, + ) + else: + log.debug( + "Templates directory does not exist", path=templates_source + ) + except Exception as e: + log.warning("Failed to copy templates directory", error=str(e)) + + # Copy restore file if it exists and is set + if scene.restore_from: + try: + restore_source = Path(scene.save_dir) / scene.restore_from + if restore_source.exists(): + # Copy to root of ZIP (same level as scene.json) + restore_dest = temp_path / scene.restore_from + shutil.copy2(restore_source, restore_dest) + log.debug( + "Copied restore file", + source=restore_source, + dest=restore_dest, + filename=scene.restore_from, + ) + else: + log.warning( + "Restore file does not exist", + path=restore_source, + filename=scene.restore_from, + ) + except Exception as e: + log.warning( + "Failed to copy restore file", + error=str(e), + filename=scene.restore_from, + ) + + # Create ZIP file + with tempfile.NamedTemporaryFile(suffix=".zip", delete=False) as temp_zip: + temp_zip_path = temp_zip.name + + try: + with zipfile.ZipFile( + temp_zip_path, "w", zipfile.ZIP_DEFLATED, compresslevel=6 + ) as zipf: + # Add all files from temp directory to ZIP + for root, _, files in os.walk(temp_path): + for file in files: + file_path = Path(root) / file + # Calculate relative path from temp_path + arcname = file_path.relative_to(temp_path) + zipf.write(file_path, arcname) + log.debug("Added file to ZIP", file=arcname) + + # Read ZIP file into memory + with open(temp_zip_path, "rb") as f: + zip_bytes = f.read() + + log.info( + "Complete scene export finished", + scene_name=scene.name, + zip_size=len(zip_bytes), + files_count=len(list(temp_path.rglob("*"))), + ) + + return zip_bytes + + finally: + # Clean up temporary ZIP file + if os.path.exists(temp_zip_path): + os.unlink(temp_zip_path) diff --git a/src/talemate/game/engine/nodes/focal.py b/src/talemate/game/engine/nodes/focal.py index af24709d..15e773ea 100644 --- a/src/talemate/game/engine/nodes/focal.py +++ b/src/talemate/game/engine/nodes/focal.py @@ -55,6 +55,7 @@ class Focal(Node): Properties: - template: The prompt template name - max_calls: The maximum number of calls to make + - response_length: The maximum length of the response Outputs: - state: The current graph state @@ -90,6 +91,16 @@ class Focal(Node): max=10, ) + response_length = PropertyField( + name="response_length", + description="The maximum length of the response", + type="int", + default=1024, + step=128, + min=1, + max=8192, + ) + def __init__(self, title="AI Function Calling", **kwargs): super().__init__(title=title, **kwargs) @@ -104,6 +115,7 @@ class Focal(Node): self.set_property("template", UNRESOLVED) self.set_property("max_calls", 1) self.set_property("retries", 0) + self.set_property("response_length", 1024) self.add_output("state") self.add_output("calls", socket_type="list") @@ -119,6 +131,7 @@ class Focal(Node): template_vars = self.get_input_value("template_vars") max_calls = self.require_number_input("max_calls", types=(int,)) retries = self.require_number_input("retries", types=(int,)) + response_length = self.require_number_input("response_length", types=(int,)) if not hasattr(agent, "client"): raise InputValueError( @@ -144,9 +157,11 @@ class Focal(Node): max_calls=max_calls, scene=scene, retries=retries, + response_length=response_length, vars={ "scene_loop": state.shared.get("scene_loop", {}), "local": state.data, + "response_length": response_length, }, **template_vars, ) diff --git a/src/talemate/game/engine/nodes/history.py b/src/talemate/game/engine/nodes/history.py index 9578d51b..daf22976 100644 --- a/src/talemate/game/engine/nodes/history.py +++ b/src/talemate/game/engine/nodes/history.py @@ -147,6 +147,29 @@ class PopHistory(Node): self.set_output_values({"message": message}) +@register("scene/history/HasHistory") +class HasHistory(Node): + """ + Check if the scene has history + """ + + def __init__(self, title="Scene Has History", **kwargs): + super().__init__(title=title, **kwargs) + + def setup(self): + self.add_output("has_history", socket_type="bool") + + async def run(self, state: GraphState): + scene: "Scene" = active_scene.get() + + messages: scene_message.SceneMessage | None = scene.last_message_of_type( + ["character", "narrator", "context_investigation"], + max_iterations=100, + ) + + self.set_output_values({"has_history": messages is not None}) + + @register("scene/history/LastMessageOfType") class LastMessageOfType(Node): """ diff --git a/src/talemate/game/engine/nodes/modules/scene/dynamic-storyline.json b/src/talemate/game/engine/nodes/modules/scene/dynamic-storyline.json index 2e105b7e..fab4cee6 100644 --- a/src/talemate/game/engine/nodes/modules/scene/dynamic-storyline.json +++ b/src/talemate/game/engine/nodes/modules/scene/dynamic-storyline.json @@ -20,7 +20,7 @@ "scope": "game" }, "x": 733, - "y": 1031, + "y": 1066, "width": 254, "height": 146, "collapsed": false, @@ -51,8 +51,8 @@ "name": "topic", "scope": "local" }, - "x": 27, - "y": 1577, + "x": 26, + "y": 1612, "width": 210, "height": 122, "collapsed": false, @@ -60,34 +60,6 @@ "registry": "state/GetState", "base_type": "core/Node" }, - "db171bea-e4a0-48b9-9a98-a7cd354d8fcf": { - "title": "Get Scene State", - "id": "db171bea-e4a0-48b9-9a98-a7cd354d8fcf", - "properties": {}, - "x": 24, - "y": 79, - "width": 140, - "height": 106, - "collapsed": false, - "inherited": false, - "registry": "scene/GetSceneState", - "base_type": "core/Node" - }, - "97204757-5fb9-4d81-ac90-20825b9a1b32": { - "title": "GET obj.history", - "id": "97204757-5fb9-4d81-ac90-20825b9a1b32", - "properties": { - "attribute": "history" - }, - "x": 204, - "y": 79, - "width": 210, - "height": 98, - "collapsed": false, - "inherited": false, - "registry": "data/Get", - "base_type": "core/Node" - }, "a14b3571-899f-47e6-90cd-6bf0fec6e4bb": { "title": "Set Introduction", "id": "a14b3571-899f-47e6-90cd-6bf0fec6e4bb", @@ -95,8 +67,8 @@ "introduction": null, "emit_history": true }, - "x": 1049, - "y": 1464, + "x": 1048, + "y": 1499, "width": 210, "height": 102, "collapsed": false, @@ -108,8 +80,8 @@ "title": "Theme", "id": "7438bc9b-4b38-4c5c-a9a8-df5ebf098691", "properties": {}, - "x": 1109, - "y": 1704, + "x": 1108, + "y": 1739, "width": 140, "height": 26, "collapsed": false, @@ -123,8 +95,8 @@ "properties": { "pass_through": false }, - "x": 279, - "y": 1434, + "x": 278, + "y": 1469, "width": 210, "height": 78, "collapsed": false, @@ -139,8 +111,8 @@ "name": "intro_generated", "scope": "game" }, - "x": 519, - "y": 1514, + "x": 518, + "y": 1549, "width": 210, "height": 122, "collapsed": false, @@ -155,39 +127,8 @@ "name": "intro_generated", "scope": "game" }, - "x": 27, - "y": 1387, - "width": 210, - "height": 122, - "collapsed": false, - "inherited": false, - "registry": "state/GetState", - "base_type": "core/Node" - }, - "be9bdeff-2232-4e00-894c-6476c4efdbc7": { - "title": "Switch", - "id": "be9bdeff-2232-4e00-894c-6476c4efdbc7", - "properties": { - "pass_through": false - }, - "x": 879, - "y": 76, - "width": 210, - "height": 78, - "collapsed": false, - "inherited": false, - "registry": "core/Switch", - "base_type": "core/Node" - }, - "912201d4-2678-45d4-a032-12707836c723": { - "title": "GET local.abort", - "id": "912201d4-2678-45d4-a032-12707836c723", - "properties": { - "name": "abort", - "scope": "local" - }, "x": 26, - "y": 608, + "y": 1422, "width": 210, "height": 122, "collapsed": false, @@ -202,7 +143,7 @@ "pass_through": false }, "x": 275, - "y": 628, + "y": 662, "width": 210, "height": 78, "collapsed": false, @@ -217,7 +158,7 @@ "exception": "StopGraphExecution" }, "x": 535, - "y": 638, + "y": 672, "width": 269, "height": 78, "collapsed": false, @@ -231,8 +172,8 @@ "properties": { "value": true }, - "x": 1004, - "y": 218, + "x": 526, + "y": 308, "width": 210, "height": 58, "collapsed": true, @@ -247,8 +188,8 @@ "name": "intro_generated", "scope": "game" }, - "x": 1164, - "y": 338, + "x": 684, + "y": 375, "width": 210, "height": 122, "collapsed": false, @@ -262,8 +203,8 @@ "properties": { "pass_through": false }, - "x": 1404, - "y": 348, + "x": 924, + "y": 385, "width": 210, "height": 78, "collapsed": true, @@ -278,8 +219,8 @@ "name": "intro_generated", "scope": "game" }, - "x": 1690, - "y": 239, + "x": 1210, + "y": 276, "width": 210, "height": 142, "collapsed": false, @@ -291,8 +232,8 @@ "title": "AND Router", "id": "99f3eba0-1baa-4f59-af90-920e8e6c1e93", "properties": {}, - "x": 1520, - "y": 249, + "x": 1040, + "y": 286, "width": 140, "height": 106, "collapsed": false, @@ -307,8 +248,8 @@ "name": "abort", "scope": "local" }, - "x": 1170, - "y": 139, + "x": 690, + "y": 176, "width": 210, "height": 142, "collapsed": false, @@ -324,8 +265,8 @@ "status": "error", "as_scene_message": true }, - "x": 1950, - "y": 239, + "x": 1470, + "y": 276, "width": 210, "height": 166, "collapsed": false, @@ -339,8 +280,8 @@ "properties": { "value": "Scene already has history, cannot generate random story premise." }, - "x": 1690, - "y": 439, + "x": 1210, + "y": 476, "width": 210, "height": 58, "collapsed": false, @@ -348,21 +289,6 @@ "registry": "data/string/MakeText", "base_type": "core/Node" }, - "07c15b6d-a275-4156-8f98-1759db36e4d0": { - "title": "Stage 1", - "id": "07c15b6d-a275-4156-8f98-1759db36e4d0", - "properties": { - "stage": 1 - }, - "x": 2197, - "y": 230, - "width": 210, - "height": 118, - "collapsed": false, - "inherited": false, - "registry": "core/Stage", - "base_type": "core/Node" - }, "afbb4f0a-5662-432b-9bc7-d30609a7d5a2": { "title": "Stage 2", "id": "afbb4f0a-5662-432b-9bc7-d30609a7d5a2", @@ -370,7 +296,7 @@ "stage": 2 }, "x": 857, - "y": 634, + "y": 668, "width": 210, "height": 118, "collapsed": false, @@ -378,37 +304,6 @@ "registry": "core/Stage", "base_type": "core/Node" }, - "3eb98fe1-b1bd-4583-bd74-23bcc155e750": { - "title": "Length", - "id": "3eb98fe1-b1bd-4583-bd74-23bcc155e750", - "properties": {}, - "x": 450, - "y": 140, - "width": 140, - "height": 26, - "collapsed": false, - "inherited": false, - "registry": "data/Length", - "base_type": "core/Node" - }, - "331ca96e-1a3c-48ea-9cfe-5b6962448063": { - "title": "Compare", - "id": "331ca96e-1a3c-48ea-9cfe-5b6962448063", - "properties": { - "operation": "greater_than", - "tolerance": 0.0001, - "a": 0, - "b": 0 - }, - "x": 630, - "y": 140, - "width": 210, - "height": 150, - "collapsed": false, - "inherited": false, - "registry": "data/number/Compare", - "base_type": "core/Node" - }, "536380db-ae7a-4ab3-bc3f-c08a47896998": { "title": "Module Style", "id": "536380db-ae7a-4ab3-bc3f-c08a47896998", @@ -434,8 +329,8 @@ "name": "analysis_instructions", "scope": "local" }, - "x": 31, - "y": 1757, + "x": 30, + "y": 1792, "width": 260, "height": 122, "collapsed": false, @@ -450,8 +345,8 @@ "name": "analysis_enabled", "scope": "local" }, - "x": 31, - "y": 1937, + "x": 30, + "y": 1972, "width": 250, "height": 122, "collapsed": false, @@ -466,8 +361,8 @@ "name": "intro_length", "scope": "local" }, - "x": 31, - "y": 2117, + "x": 30, + "y": 2152, "width": 250, "height": 122, "collapsed": false, @@ -479,8 +374,8 @@ "title": "Generate Storyline", "id": "958c9db5-6df8-4d04-919d-c0a4b42fe3f8", "properties": {}, - "x": 771, - "y": 1676, + "x": 770, + "y": 1711, "width": 228, "height": 106, "collapsed": false, @@ -494,8 +389,8 @@ "properties": { "stage": 4 }, - "x": 1311, - "y": 1446, + "x": 1310, + "y": 1481, "width": 210, "height": 118, "collapsed": false, @@ -511,7 +406,7 @@ "emit_history": true }, "x": 1019, - "y": 1046, + "y": 1081, "width": 210, "height": 102, "collapsed": false, @@ -526,7 +421,7 @@ "pass_through": false }, "x": 266, - "y": 1133, + "y": 1168, "width": 210, "height": 78, "collapsed": false, @@ -542,7 +437,7 @@ "scope": "game" }, "x": 26, - "y": 1133, + "y": 1168, "width": 210, "height": 122, "collapsed": false, @@ -558,7 +453,7 @@ "scope": "local" }, "x": 26, - "y": 913, + "y": 948, "width": 210, "height": 122, "collapsed": false, @@ -573,7 +468,7 @@ "pass_through": true }, "x": 276, - "y": 933, + "y": 968, "width": 210, "height": 78, "collapsed": false, @@ -586,7 +481,7 @@ "id": "0c73a403-ada1-45fa-ac3d-9a4890f4c9d0", "properties": {}, "x": 546, - "y": 1033, + "y": 1068, "width": 140, "height": 106, "collapsed": false, @@ -601,7 +496,7 @@ "stage": 3 }, "x": 1266, - "y": 1043, + "y": 1078, "width": 210, "height": 118, "collapsed": false, @@ -802,6 +697,65 @@ "inherited": false, "registry": "core/ModuleProperty", "base_type": "core/Node" + }, + "2d28f380-7998-452a-be35-bdb0fc2367f5": { + "title": "Has History", + "id": "2d28f380-7998-452a-be35-bdb0fc2367f5", + "properties": {}, + "x": 65, + "y": 259, + "width": 140, + "height": 26, + "collapsed": false, + "inherited": false, + "registry": "scene/history/HasHistory", + "base_type": "core/Node" + }, + "be9bdeff-2232-4e00-894c-6476c4efdbc7": { + "title": "Switch", + "id": "be9bdeff-2232-4e00-894c-6476c4efdbc7", + "properties": { + "pass_through": false + }, + "x": 265, + "y": 249, + "width": 210, + "height": 78, + "collapsed": false, + "inherited": false, + "registry": "core/Switch", + "base_type": "core/Node" + }, + "07c15b6d-a275-4156-8f98-1759db36e4d0": { + "title": "Stage 1", + "id": "07c15b6d-a275-4156-8f98-1759db36e4d0", + "properties": { + "stage": 1 + }, + "x": 1714, + "y": 265, + "width": 210, + "height": 118, + "collapsed": false, + "inherited": false, + "registry": "core/Stage", + "base_type": "core/Node" + }, + "912201d4-2678-45d4-a032-12707836c723": { + "title": "GET local.abort", + "id": "912201d4-2678-45d4-a032-12707836c723", + "properties": { + "name": "abort", + "scope": "local" + }, + "x": 30, + "y": 644, + "width": 210, + "height": 122, + "collapsed": false, + "inherited": false, + "registry": "state/GetState", + "base_type": "core/Node" } }, "edges": { @@ -814,12 +768,6 @@ "2a4e0aed-e85a-4fba-ae30-630c71f91db9.value": [ "958c9db5-6df8-4d04-919d-c0a4b42fe3f8.topic" ], - "db171bea-e4a0-48b9-9a98-a7cd354d8fcf.scene": [ - "97204757-5fb9-4d81-ac90-20825b9a1b32.object" - ], - "97204757-5fb9-4d81-ac90-20825b9a1b32.value": [ - "3eb98fe1-b1bd-4583-bd74-23bcc155e750.object" - ], "a14b3571-899f-47e6-90cd-6bf0fec6e4bb.state": [ "ca241a90-daa3-40e4-9b7a-b857c334cd39.state" ], @@ -833,12 +781,6 @@ "37313611-7de1-478b-8840-e5ea7ba856fe.value": [ "11965433-d217-47d1-bc37-8808426798ce.value" ], - "be9bdeff-2232-4e00-894c-6476c4efdbc7.yes": [ - "626e25be-9d1f-460a-9611-7adc1a1fb28f.state" - ], - "912201d4-2678-45d4-a032-12707836c723.value": [ - "91636e7d-d05b-4f5b-a234-6e4a1e6a6412.value" - ], "91636e7d-d05b-4f5b-a234-6e4a1e6a6412.yes": [ "4637ce9d-ac09-4f68-85d9-5e9ac9639d3d.state" ], @@ -870,12 +812,6 @@ "c3398b66-a29a-4453-b17e-d9f8f5fb7efa.value": [ "514bede6-cfda-42eb-8285-8c11181e62f6.message" ], - "3eb98fe1-b1bd-4583-bd74-23bcc155e750.length": [ - "331ca96e-1a3c-48ea-9cfe-5b6962448063.a" - ], - "331ca96e-1a3c-48ea-9cfe-5b6962448063.result": [ - "be9bdeff-2232-4e00-894c-6476c4efdbc7.value" - ], "907bc1c7-c19b-4a28-ba0c-c386bbfd0aa9.value": [ "958c9db5-6df8-4d04-919d-c0a4b42fe3f8.analysis_instructions" ], @@ -950,13 +886,22 @@ ], "3f749c9f-71b0-4e9c-b99d-859e53f20569.value": [ "c327b0c4-900e-4d27-a0b9-0d47e8479732.value" + ], + "2d28f380-7998-452a-be35-bdb0fc2367f5.has_history": [ + "be9bdeff-2232-4e00-894c-6476c4efdbc7.value" + ], + "be9bdeff-2232-4e00-894c-6476c4efdbc7.yes": [ + "626e25be-9d1f-460a-9611-7adc1a1fb28f.state" + ], + "912201d4-2678-45d4-a032-12707836c723.value": [ + "91636e7d-d05b-4f5b-a234-6e4a1e6a6412.value" ] }, "groups": [ { "title": "Generate Premise - Stage 4", "x": 1, - "y": 1283, + "y": 1318, "width": 1557, "height": 980, "color": "#3f789e", @@ -976,9 +921,9 @@ { "title": "Scene valid for randomized premise? - Stage 1", "x": 1, - "y": -3, - "width": 2431, - "height": 528, + "y": -24, + "width": 1947, + "height": 583, "color": "#b58b2a", "font_size": 24, "inherited": false @@ -986,7 +931,7 @@ { "title": "Reset - Stage 3", "x": 1, - "y": 773, + "y": 808, "width": 1500, "height": 507, "color": "#88A", @@ -996,7 +941,7 @@ { "title": "Abort - Stage 2", "x": 1, - "y": 527, + "y": 562, "width": 1149, "height": 243, "color": "#b58b2a", @@ -1007,59 +952,59 @@ "comments": [ { "text": "SetState here so we don't infinitely try to generate premise if something goes wrong.", - "x": 519, - "y": 1364, + "x": 518, + "y": 1399, "width": 200, "inherited": false }, { "text": "The scene already has a history, we set the abort flag.", - "x": 1180, - "y": 19, + "x": 700, + "y": 56, "width": 200, "inherited": false }, { "text": "If this is the first attempt to generate a dynamic premise ...", - "x": 1480, - "y": 129, + "x": 1000, + "y": 166, "width": 200, "inherited": false }, { "text": "... flag that an intro generation attempt was made ...", - "x": 1700, - "y": 129, + "x": 1220, + "y": 166, "width": 200, "inherited": false }, { "text": "... and send a notification to the user.", - "x": 1950, - "y": 139, + "x": 1470, + "y": 176, "width": 200, "inherited": false }, - { - "text": "Checking if the scene already has messages in its history.", - "x": 470, - "y": 40, - "width": 343, - "inherited": false - }, - { - "text": "When a scene already has meessages in its history it does not make sense to generate a new dynamic premise. At least in the current iteration of this module. So this stage checks for that and initiates the abort flag if needed.", - "x": 120, - "y": 300, - "width": 408, - "inherited": false - }, { "text": "When we are generating the first introduction OR when we are resetting the process: Remove the current introductory text.", "x": 546, - "y": 853, + "y": 888, "width": 200, "inherited": false + }, + { + "text": "Checking if the scene already has messages in its history.", + "x": 45, + "y": 139, + "width": 343, + "inherited": false + }, + { + "text": "When a scene already has meessages in its history it does not make sense to generate a new dynamic premise. At least in the current iteration of this module. So this stage checks for that and initiates the abort flag if needed.", + "x": 37, + "y": 434, + "width": 408, + "inherited": false } ], "extends": null, diff --git a/src/talemate/game/engine/nodes/scene.py b/src/talemate/game/engine/nodes/scene.py index 9693440f..66533e51 100644 --- a/src/talemate/game/engine/nodes/scene.py +++ b/src/talemate/game/engine/nodes/scene.py @@ -223,11 +223,16 @@ class MakeCharacter(Node): actor = ActorCls(character, get_agent("conversation")) + log.warning( + "Make character", + character=character, + add_to_scene=add_to_scene, + is_active=is_active, + ) if add_to_scene: await scene.add_actor(actor) if not is_active: await deactivate_character(character) - self.set_output_values({"actor": actor, "character": character}) diff --git a/src/talemate/game/engine/nodes/string.py b/src/talemate/game/engine/nodes/string.py index bf5193c3..570a20a5 100644 --- a/src/talemate/game/engine/nodes/string.py +++ b/src/talemate/game/engine/nodes/string.py @@ -5,6 +5,24 @@ from .registry import register log = structlog.get_logger("talemate.game.engine.nodes.string") +@register("data/string/AsString") +class AsString(Node): + """ + Converts a value to a string + """ + + def __init__(self, title="As String", **kwargs): + super().__init__(title=title, **kwargs) + + def setup(self): + self.add_input("value", socket_type="any") + self.add_output("value", socket_type="str") + + async def run(self, state: GraphState): + value = self.normalized_input_value("value") + self.set_output_values({"value": str(value)}) + + @register("data/string/Make") class MakeString(Node): """Creates a string diff --git a/src/talemate/game/focal/__init__.py b/src/talemate/game/focal/__init__.py index 0aea270b..548778c3 100644 --- a/src/talemate/game/focal/__init__.py +++ b/src/talemate/game/focal/__init__.py @@ -8,7 +8,7 @@ This does NOT use API specific function calling (like openai or anthropic), but import structlog import traceback -from typing import Callable +from typing import Callable, TYPE_CHECKING from contextvars import ContextVar from talemate.client.base import ClientBase @@ -16,9 +16,14 @@ from talemate.prompts.base import Prompt from talemate.util.data import ( extract_data, ) +from talemate.instance import get_agent from .schema import Argument, Call, Callback, State + +if TYPE_CHECKING: + from talemate.agents.director import DirectorAgent + __all__ = [ "Argument", "Call", @@ -62,12 +67,14 @@ class Focal: max_calls: int = 5, retries: int = 0, schema_format: str = "json", + response_length: int = 1024, **kwargs, ): self.client = client self.context = kwargs self.max_calls = max_calls self.retries = retries + self.response_length = response_length self.state = State(schema_format=schema_format) self.callbacks = {callback.name: callback for callback in callbacks} @@ -101,7 +108,7 @@ class Focal: response = await Prompt.request( template_name, self.client, - "analyze_long", + f"analyze_{self.response_length}", vars={ **self.context, "focal": self, @@ -146,6 +153,8 @@ class Focal: calls_made = 0 + director: "DirectorAgent" = get_agent("director") + for call in calls: if calls_made >= self.max_calls: log.warning("focal.execute.max_calls_reached", max_calls=self.max_calls) @@ -165,6 +174,9 @@ class Focal: log.debug( f"focal.execute - Calling {callback.name}", arguments=call.arguments ) + + await director.log_function_call(call) + result = await callback.fn(**call.arguments) call.result = result call.called = True @@ -205,7 +217,7 @@ class Focal: _, calls_json = await Prompt.request( "focal.extract_calls", self.client, - "analyze_long", + f"analyze_{self.response_length}", vars={ **self.context, "text": response, diff --git a/src/talemate/instance.py b/src/talemate/instance.py index 0b1699c5..64a957c8 100644 --- a/src/talemate/instance.py +++ b/src/talemate/instance.py @@ -9,8 +9,11 @@ import structlog import talemate.agents as agents import talemate.client as clients import talemate.client.bootstrap as bootstrap +from talemate.client.base import ClientStatus from talemate.emit import emit from talemate.emit.signals import handlers +import talemate.emit.async_signals as async_signals +from talemate.config import get_config, Config log = structlog.get_logger("talemate") @@ -18,56 +21,29 @@ AGENTS = {} CLIENTS = {} -def get_agent(typ: str, *create_args, **create_kwargs): +def get_agent(typ: str): agent = AGENTS.get(typ) - if agent: - return agent + if not agent: + raise KeyError(f"Agent {typ} has not been instantiated") - if create_args or create_kwargs: - cls = agents.get_agent_class(typ) - agent = cls(*create_args, **create_kwargs) - set_agent(typ, agent) - return agent + return agent -def set_agent(typ, agent): - AGENTS[typ] = agent - - -async def destroy_client(name: str, config: dict): +async def destroy_client(name: str): client = CLIENTS.get(name) if client: - await client.destroy(config) + await client.destroy() del CLIENTS[name] -def get_client(name: str, *create_args, **create_kwargs): +def get_client(name: str): client = CLIENTS.get(name) - system_prompts = create_kwargs.pop("system_prompts", None) + if not client: + raise KeyError(f"Client {name} has not been instantiated") - if client: - if create_kwargs: - if system_prompts: - client.set_system_prompts(system_prompts) - client.reconfigure(**create_kwargs) - return client - - if "type" in create_kwargs: - typ = create_kwargs.get("type") - cls = clients.get_client_class(typ) - client = cls(name=name, *create_args, **create_kwargs) - - if system_prompts: - client.set_system_prompts(system_prompts) - - set_client(name, client) - return client - - -def set_client(name, client): - CLIENTS[name] = client + return client def agent_types(): @@ -198,3 +174,142 @@ async def agent_ready_checks(): await agent.ready_check() elif agent and not agent.enabled: await agent.setup_check() + + +def get_active_client(): + for client in CLIENTS.values(): + if client.enabled: + return client + return None + + +async def instantiate_agents(): + config: Config = get_config() + + for typ, cls in agents.AGENT_CLASSES.items(): + if typ in AGENTS: + continue + + agent_config = config.agents.get(typ) + if agent_config: + _agent_config = agent_config.model_dump() + + client_name = _agent_config.pop("client", None) + if client_name: + _agent_config["client"] = CLIENTS.get(client_name) + + _agent_config.pop("name", None) + actions = _agent_config.pop("actions", None) + enabled = _agent_config.pop("enabled", True) + + agent = cls(**_agent_config) + + if actions: + await agent.apply_config(actions=actions) + + if not enabled and agent.has_toggle: + agent.is_enabled = False + elif enabled is True and agent.has_toggle: + agent.is_enabled = True + + AGENTS[typ] = agent + await agent.emit_status() + else: + agent = cls() + AGENTS[typ] = agent + await agent.emit_status() + + await ensure_agent_llm_client() + + +async def instantiate_clients(): + config: Config = get_config() + for name, client_config in config.clients.items(): + if name in CLIENTS: + continue + + client = clients.get_client_class(client_config.type)( + **client_config.model_dump() + ) + CLIENTS[name] = client + + await emit_clients_status() + + +async def configure_agents(): + config: Config = get_config() + for name, agent_config in config.agents.items(): + agent = AGENTS.get(name) + if not agent: + log.warn("agent not found", name=name) + continue + + await agent.apply_config(**agent_config.model_dump()) + await agent.emit_status() + + await ensure_agent_llm_client() + + +async def ensure_agent_llm_client(): + config: Config = get_config() + for name, agent in AGENTS.items(): + agent_config = config.agents.get(name) + + if not agent: + log.warn("agent not found", name=name) + continue + + if not agent.requires_llm_client: + continue + + client_name = agent_config.client if agent_config else None + + if not client_name: + client = get_active_client() + + elif not CLIENTS.get(client_name): + client = get_active_client() + + else: + client = CLIENTS.get(client_name) + if client and not client.enabled: + client = get_active_client() + + log.debug( + "ensure_agent_llm_client", + agent=agent.agent_type, + client=client.client_type if client else None, + ) + + if agent.client != client: + agent.client = client + await agent.emit_status() + + +async def purge_clients(): + """Checks for clients in CLIENTS that are not longer in the config + and removes them + """ + config: Config = get_config() + for name, _ in list(CLIENTS.items()): + if name in config.clients: + continue + await destroy_client(name) + + +async def on_config_changed(config: Config): + await emit_clients_status() + emit_agents_status() + + +async def on_client_disabled(client_status: ClientStatus): + await ensure_agent_llm_client() + + +async def on_client_enabled(client_status: ClientStatus): + await ensure_agent_llm_client() + + +async_signals.get("config.changed").connect(on_config_changed) +async_signals.get("client.disabled").connect(on_client_disabled) +async_signals.get("client.enabled").connect(on_client_enabled) diff --git a/src/talemate/load.py b/src/talemate/load.py index aaf7e0b9..90bbad16 100644 --- a/src/talemate/load.py +++ b/src/talemate/load.py @@ -1,6 +1,12 @@ import enum import json import os +import shutil +import tempfile +import uuid +import zipfile +from pathlib import Path +from typing import TYPE_CHECKING import structlog @@ -8,7 +14,8 @@ import talemate.instance as instance from talemate import Actor, Character, Player, Scene from talemate.instance import get_agent from talemate.character import deactivate_character -from talemate.config import load_config +from talemate.config import get_config, Config +from talemate.config.schema import GamePlayerCharacter from talemate.context import SceneIsLoading from talemate.exceptions import UnknownDataSpec from talemate.game.state import GameState @@ -27,9 +34,15 @@ from talemate.world_state import WorldState from talemate.game.engine.nodes.registry import import_scene_node_definitions from talemate.scene.intent import SceneIntent from talemate.history import validate_history +import talemate.agents.tts.voice_library as voice_library +from talemate.path import SCENES_DIR + +if TYPE_CHECKING: + from talemate.agents.director import DirectorAgent __all__ = [ "load_scene", + "load_scene_from_zip", "load_character_from_image", "load_character_from_json", "transfer_character", @@ -40,6 +53,7 @@ log = structlog.get_logger("talemate.load") class ImportSpec(str, enum.Enum): talemate = "talemate" + talemate_complete = "talemate_complete" chara_card_v0 = "chara_card_v0" chara_card_v2 = "chara_card_v2" chara_card_v1 = "chara_card_v1" @@ -47,7 +61,7 @@ class ImportSpec(str, enum.Enum): @set_loading("Loading scene...") -async def load_scene(scene, file_path, conv_client, reset: bool = False): +async def load_scene(scene, file_path, reset: bool = False): """ Load the scene data from the given file path. """ @@ -56,7 +70,7 @@ async def load_scene(scene, file_path, conv_client, reset: bool = False): with SceneIsLoading(scene): if file_path == "$NEW_SCENE$": return await load_scene_from_data( - scene, new_scene(), conv_client, reset=True, empty=True + scene, new_scene(), reset=True, empty=True ) ext = os.path.splitext(file_path)[1].lower() @@ -66,6 +80,10 @@ async def load_scene(scene, file_path, conv_client, reset: bool = False): if ext in [".jpg", ".png", ".jpeg", ".webp"]: return await load_scene_from_character_card(scene, file_path) + # a zip file was uploaded, extract and load complete scene + if ext == ".zip": + return await load_scene_from_zip(scene, file_path, reset) + # a json file was uploaded, load the scene data with open(file_path, "r") as f: scene_data = json.load(f) @@ -79,9 +97,7 @@ async def load_scene(scene, file_path, conv_client, reset: bool = False): return await load_scene_from_character_card(scene, file_path) # if it is a talemate scene, load it - return await load_scene_from_data( - scene, scene_data, conv_client, reset, name=file_path - ) + return await load_scene_from_data(scene, scene_data, reset, name=file_path) finally: await scene.add_to_recent_scenes() @@ -115,10 +131,10 @@ async def load_scene_from_character_card(scene, file_path): Load a character card (tavern etc.) from the given file path. """ - director = get_agent("director") - LOADING_STEPS = 5 + director: "DirectorAgent" = get_agent("director") + LOADING_STEPS = 6 if director.auto_direct_enabled: - LOADING_STEPS += 3 + LOADING_STEPS += 2 loading_status = LoadingStatus(LOADING_STEPS) loading_status("Loading character card...") @@ -136,9 +152,9 @@ async def load_scene_from_character_card(scene, file_path): character = load_character_from_image(file_path, image_format) image = True - conversation = scene.get_helper("conversation").agent - creator = scene.get_helper("creator").agent - memory = scene.get_helper("memory").agent + conversation = instance.get_agent("conversation") + creator = instance.get_agent("creator") + memory = instance.get_agent("memory") actor = Actor(character, conversation) @@ -194,7 +210,7 @@ async def load_scene_from_character_card(scene, file_path): if character.base_attributes.get("description"): character.description = character.base_attributes.pop("description") - await character.commit_to_memory(scene.get_helper("memory").agent) + await character.commit_to_memory(memory) log.debug("base_attributes parsed", base_attributes=character.base_attributes) except Exception as e: @@ -206,17 +222,20 @@ async def load_scene_from_character_card(scene, file_path): scene.assets.set_cover_image_from_file_path(file_path) character.cover_image = scene.assets.cover_image + # assign tts voice to character + await director.assign_voice_to_character(character) + # if auto direct is enabled, generate a story intent # and then set the scene intent try: + loading_status("Generating story intent...") + creator = get_agent("creator") + story_intent = await creator.contextual_generate_from_args( + context="scene intent:overall", + length=256, + ) + scene.intent_state.intent = story_intent if director.auto_direct_enabled: - loading_status("Generating story intent...") - creator = get_agent("creator") - story_intent = await creator.contextual_generate_from_args( - context="story intent:overall", - length=256, - ) - scene.intent_state.intent = story_intent loading_status("Generating scene types...") await director.auto_direct_generate_scene_types( instructions=story_intent, @@ -229,15 +248,36 @@ async def load_scene_from_character_card(scene, file_path): scene.saved = False - await scene.save_restore("initial.json") - scene.restore_from = "initial.json" + restore_file = "initial.json" + + # check if restore_file exists already + if os.path.exists(Path(scene.save_dir) / restore_file): + uid = str(uuid.uuid4())[:8] + restore_file = f"initial-{uid}.json" + log.warning( + "Restore file already exists, creating a new one", + restore_file=restore_file, + ) + + await scene.save_restore(restore_file) + scene.restore_from = restore_file import_scene_node_definitions(scene) + save_file = f"{scene.project_name}.json" + + # check if save_file exists already + if os.path.exists(Path(scene.save_dir) / save_file): + uid = str(uuid.uuid4())[:8] + save_file = f"{scene.project_name}-{uid}.json" + log.warning( + "Save file already exists, creating a new one", + save_file=save_file, + ) await scene.save( save_as=True, auto=True, - copy_name=f"{scene.project_name}.json", + copy_name=save_file, ) return scene @@ -246,15 +286,15 @@ async def load_scene_from_character_card(scene, file_path): async def load_scene_from_data( scene, scene_data, - conv_client, reset: bool = False, name: str | None = None, empty: bool = False, ): loading_status = LoadingStatus(1) reset_message_id() + config: Config = get_config() - memory = scene.get_helper("memory").agent + memory = instance.get_agent("memory") scene.description = scene_data.get("description", "") scene.intro = scene_data.get("intro", "") or scene.description @@ -318,27 +358,199 @@ async def load_scene_from_data( scene.inactive_characters.pop(character.name) if not character.is_player: - agent = instance.get_agent("conversation", client=conv_client) - actor = Actor(character, agent) + agent = instance.get_agent("conversation") + actor = Actor(character=character, agent=agent) else: - actor = Player(character, None) + actor = Player(character=character, agent=None) await scene.add_actor(actor) # if there is nio player character, add the default player character await handle_no_player_character( scene, - add_default_character=scene.config.get("game", {}) - .get("general", {}) - .get("add_default_character", True), + add_default_character=config.game.general.add_default_character, ) # the scene has been saved before (since we just loaded it), so we set the saved flag to True # as long as the scene has a memory_id. scene.saved = "memory_id" in scene_data + # load the scene voice library + scene.voice_library = await voice_library.load_scene_voice_library(scene) + log.debug("scene voice library", voice_library=scene.voice_library) + return scene +@set_loading("Importing scene archive...") +async def load_scene_from_zip(scene, zip_path, reset: bool = False): + """ + Load a complete scene from a ZIP file containing scene.json and all assets/nodes/info/templates + """ + log.info("Loading complete scene from ZIP", zip_path=zip_path, reset=reset) + + # Verify ZIP file + if not zipfile.is_zipfile(zip_path): + raise ValueError(f"File is not a valid ZIP archive: {zip_path}") + + # Extract ZIP to temporary directory + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + + log.debug("Extracting ZIP archive", zip_path=zip_path, temp_dir=temp_dir) + + with zipfile.ZipFile(zip_path, "r") as zipf: + # Check if scene.json exists in ZIP + if "scene.json" not in zipf.namelist(): + raise ValueError( + "ZIP archive does not contain required scene.json file" + ) + + # Extract all files + zipf.extractall(temp_path) + log.debug("Extracted ZIP contents", files=len(zipf.namelist())) + + # Load scene.json + scene_json_path = temp_path / "scene.json" + with open(scene_json_path, "r", encoding="utf-8") as f: + scene_data = json.load(f) + + log.debug( + "Loaded scene JSON from ZIP", scene_name=scene_data.get("name", "Unknown") + ) + + # Generate unique scene name for ZIP imports to avoid conflicts + # The scene's save_dir is derived from its name, so we set the name + base_scene_name = scene_data.get("name", "imported-scene") + + # Handle directory name conflicts by adding suffix to the scene name + scene_name = base_scene_name + counter = 1 + + # Convert scene name to project name format (same as Scene.project_name property) + def to_project_name(name): + return name.replace(" ", "-").replace("'", "").lower() + + potential_dir = os.path.join(str(SCENES_DIR), to_project_name(scene_name)) + + while os.path.exists(potential_dir): + scene_name = f"{base_scene_name}-{counter}" + potential_dir = os.path.join(str(SCENES_DIR), to_project_name(scene_name)) + counter += 1 + if counter > 100: # Safety limit + scene_name = f"{base_scene_name}-{uuid.uuid4().hex[:8]}" + potential_dir = os.path.join( + str(SCENES_DIR), to_project_name(scene_name) + ) + break + + # Set the scene name (which will determine save_dir via the property) + scene.name = scene_name + + log.debug( + "Generated unique scene name for ZIP import", + original_name=base_scene_name, + final_name=scene_name, + project_name=to_project_name(scene_name), + save_dir=scene.save_dir, + ) + + # Create scene save directory (this happens automatically via the save_dir property) + # but we explicitly access it to trigger directory creation + actual_save_dir = scene.save_dir # This triggers directory creation + log.debug("Scene save directory prepared", save_dir=actual_save_dir) + + # Restore assets if they exist in ZIP + assets_source = temp_path / "assets" + if assets_source.exists(): + assets_dest = Path(scene.save_dir) / "assets" + shutil.copytree(assets_source, assets_dest) + log.debug("Loaded assets directory", source=assets_source, dest=assets_dest) + + # Restore nodes if they exist in ZIP + nodes_source = temp_path / "nodes" + if nodes_source.exists(): + nodes_dest = Path(scene.save_dir) / "nodes" + shutil.copytree(nodes_source, nodes_dest) + log.debug("Loaded nodes directory", source=nodes_source, dest=nodes_dest) + + # Restore info if it exists in ZIP + info_source = temp_path / "info" + if info_source.exists(): + info_dest = Path(scene.save_dir) / "info" + shutil.copytree(info_source, info_dest) + log.debug("Loaded info directory", source=info_source, dest=info_dest) + + # Restore templates if they exist in ZIP + templates_source = temp_path / "templates" + if templates_source.exists(): + templates_dest = Path(scene.save_dir) / "templates" + shutil.copytree(templates_source, templates_dest) + log.debug( + "Loaded templates directory", + source=templates_source, + dest=templates_dest, + ) + + # Restore restore file if it exists in ZIP and is referenced in scene_data + restore_filename = scene_data.get("restore_from") + if restore_filename: + restore_source = temp_path / restore_filename + if restore_source.exists(): + restore_dest = Path(scene.save_dir) / restore_filename + shutil.copy2(restore_source, restore_dest) + log.debug( + "Restored restore file", + source=restore_source, + dest=restore_dest, + filename=restore_filename, + ) + else: + log.warning( + "Restore file referenced in scene data but not found in ZIP, unsetting restore_from", + filename=restore_filename, + ) + scene.restore_from = None + + # Update scene_data with the conflict-resolved name so saves go to the right directory + scene_data = scene_data.copy() # Don't modify the original + scene_data["name"] = scene.name # Use the conflict-resolved name + + log.info( + "Complete scene import finished", + final_scene_name=scene.name, + save_dir=scene.save_dir, + ) + + # Load the scene data with the updated name + # Use the scene name (without .zip extension) for the filename + zip_basename = os.path.basename(zip_path) + clean_name = ( + zip_basename.replace(".zip", "") + if zip_basename.endswith(".zip") + else zip_basename + ) + result = await load_scene_from_data(scene, scene_data, reset, name=clean_name) + + # If no restore_from is set, set it to the initial.json file + if not scene.restore_from: + scene.restore_from = "initial.json" + await scene.save_restore("initial.json") + log.debug( + "Set restore_from to initial.json", restore_from=scene.restore_from + ) + + # Save the scene to ensure the JSON file is written to the correct directory + # This ensures both the assets and the scene JSON are in the same place + await scene.save(auto=False, force=True) + log.debug( + "Saved imported scene to directory", + save_dir=scene.save_dir, + filename=scene.filename, + ) + + return result + + async def transfer_character(scene, scene_json_path, character_name): """ Load a character from a scene json file and add it to the current scene. @@ -351,7 +563,7 @@ async def transfer_character(scene, scene_json_path, character_name): with open(scene_json_path, "r") as f: scene_data = json.load(f) - agent = scene.get_helper("conversation").agent + agent = instance.get_agent("conversation") # Find the character in the characters list for character_data in scene_data["characters"]: @@ -461,8 +673,12 @@ def character_from_chara_data(data: dict) -> Character: Generates a barebones character from a character card data dictionary. """ - character = Character("", "", "") - character.color = "red" + character = Character( + name="UNKNOWN", + description="", + greeting_text="", + ) + if "name" in data: character.name = data["name"] @@ -519,21 +735,20 @@ def default_player_character() -> Player | None: Return a default player character. :return: Default player character. """ - default_player_character = ( - load_config().get("game", {}).get("default_player_character", {}) - ) - name = default_player_character.get("name") + config: Config = get_config() + default_player_character: GamePlayerCharacter = config.game.default_player_character + name = default_player_character.name if not name: # We don't have a valid default player character, so we return None return None - color = default_player_character.get("color", "cyan") - description = default_player_character.get("description", "") + color = default_player_character.color + description = default_player_character.description return Player( Character( - name, + name=name, description=description, greeting_text="", color=color, diff --git a/src/talemate/path.py b/src/talemate/path.py new file mode 100644 index 00000000..6705b4ce --- /dev/null +++ b/src/talemate/path.py @@ -0,0 +1,17 @@ +from pathlib import Path + +__all__ = [ + "TALEMATE_ROOT", + "SCENES_DIR", + "TEMPLATES_DIR", + "TTS_DIR", + "CONFIG_FILE", +] + +TALEMATE_ROOT = Path(__file__).parent.parent.parent +SCENES_DIR = TALEMATE_ROOT / "scenes" +TEMPLATES_DIR = TALEMATE_ROOT / "templates" +TTS_DIR = TALEMATE_ROOT / "tts" + + +CONFIG_FILE = TALEMATE_ROOT / "config.yaml" diff --git a/src/talemate/prompts/base.py b/src/talemate/prompts/base.py index cb6c9428..a670588b 100644 --- a/src/talemate/prompts/base.py +++ b/src/talemate/prompts/base.py @@ -24,7 +24,7 @@ import structlog import talemate.instance as instance import talemate.thematic_generators as thematic_generators -from talemate.config import load_config +from talemate.config import get_config from talemate.context import regeneration_context, active_scene from talemate.emit import emit from talemate.exceptions import LLMAccuracyError, RenderPromptError @@ -311,7 +311,7 @@ class Prompt: @property def config(self): if not hasattr(self, "_config"): - self._config = load_config() + self._config = get_config() return self._config def __str__(self): @@ -863,6 +863,9 @@ class Prompt: # Extract YAML from markdown code blocks if "```yaml" in response and "```" in response.split("```yaml", 1)[1]: yaml_block = response.split("```yaml", 1)[1].split("```", 1)[0] + # Starts with ```yaml but has not ``` at the end + elif "```yaml" in response and "```" not in response.split("```yaml", 1)[1]: + yaml_block = response.split("```yaml", 1)[1] elif "```" in response: # Try any code block as fallback yaml_block = response.split("```", 1)[1].split("```", 1)[0] @@ -1027,7 +1030,9 @@ class Prompt: self.client = client - response = await client.send_prompt(str(self), kind=kind) + response = await client.send_prompt( + str(self), kind=kind, data_expected=self.data_response + ) # Handle prepared response prepending based on response format if not self.data_response: @@ -1041,22 +1046,32 @@ class Prompt: ) json_start = response.lstrip().startswith("{") - yaml_block = response.lstrip().startswith("```yaml") + yaml_block = "```yaml" in response + json_block = "```json" in response - # If response doesn't start with expected format markers, prepend the prepared response - if (format_type == "json" and not json_start) or ( - format_type == "yaml" and not yaml_block - ): - pad = " " if self.pad_prepended_response else "" - if format_type == "yaml": - if self.client.can_be_coerced: - response = self.prepared_response + response.rstrip() + if format_type == "json" and json_block: + response = response.split("```json", 1)[1].split("```", 1)[0] + elif format_type == "yaml" and yaml_block: + response = response.split("```yaml", 1)[1].split("```", 1)[0].strip() + else: + # If response doesn't start with expected format markers, prepend the prepared response + if (format_type == "json" and not json_start) or ( + format_type == "yaml" and not yaml_block + ): + pad = " " if self.pad_prepended_response else "" + if format_type == "yaml": + if self.client.can_be_coerced: + response = self.prepared_response + response.rstrip() + else: + response = ( + self.prepared_response.rstrip() + + "\n " + + response.rstrip() + ) else: response = ( - self.prepared_response.rstrip() + "\n " + response.rstrip() + self.prepared_response.rstrip() + pad + response.strip() ) - else: - response = self.prepared_response.rstrip() + pad + response.strip() if self.eval_response: return await self.evaluate(response) diff --git a/src/talemate/prompts/templates/common/narrative-patterns.jinja2 b/src/talemate/prompts/templates/common/narrative-patterns.jinja2 new file mode 100644 index 00000000..e66f8f9d --- /dev/null +++ b/src/talemate/prompts/templates/common/narrative-patterns.jinja2 @@ -0,0 +1,66 @@ +{% set response_patterns = [ + "PROSE DIALOGUE PROSE", + "SHORT_DIALOGUE PROSE DIALOGUE", + "DIALOGUE PROSE SHORT_DIALOGUE", + "PROSE SHORT_DIALOGUE PROSE", + "SHORT_DIALOGUE PROSE SHORT_DIALOGUE", + "PROSE DIALOGUE SHORT_DIALOGUE", + "DIALOGUE PROSE DIALOGUE", + "SHORT_DIALOGUE PROSE PROSE", + "PROSE DIALOGUE PROSE DIALOGUE", + "DIALOGUE PROSE PROSE" +] %} +{% set selected_pattern = response_patterns[range(0, response_patterns|length)|random] %} + +**CRITICAL: Follow this exact response structure**: {{ selected_pattern }} + +**DO NOT include these labels in your response. These are structural guidelines only:** +- PROSE: 2-4 sentences of narrative description (actions, thoughts, environment, sensory details) +- DIALOGUE: 1-2 sentences of spoken words in quotes +- SHORT_DIALOGUE: Few words of spoken words in quotes + +**Format**: Write flowing narrative prose without character name prefixes, section labels, or formatting markers. The pattern shows the STRUCTURE to follow, not text to include. + +**Example for your selected pattern ({{ selected_pattern }})**: + +{% if selected_pattern == "PROSE DIALOGUE PROSE" %} +``` example +The rain drummed against the window as shadows danced across the empty room. A distant clock chimed midnight, its echo swallowed by the storm. "Something's not right about this place," she whispered, eyes scanning the peeling wallpaper for any sign of what might be wrong. The floorboards groaned beneath her feet as if the house itself was responding to her presence. +``` +{% elif selected_pattern == "SHORT_DIALOGUE PROSE DIALOGUE" %} +``` example +"Wait." She froze at the warehouse entrance, her instincts screaming danger. Dust motes swirled in the afternoon light streaming through broken windows, and somewhere in the distance, metal creaked against metal. "I need to call for backup first," she muttered, her voice steady despite the unease crawling up her spine. +``` +{% elif selected_pattern == "DIALOGUE PROSE SHORT_DIALOGUE" %} +``` example +"I've been waiting here for hours," she said, stepping out from behind the marble pillar. "Though I wasn't sure anyone would actually come." The museum's grand hall stretched endlessly around her, filled with ancient artifacts that seemed to watch from their glass cases. "Finally." +``` +{% elif selected_pattern == "PROSE SHORT_DIALOGUE PROSE" %} +``` example +The cafe bustled with morning energy as steam rose from countless coffee cups. Conversations blended into a comfortable hum of urban life around her. "One more chance," she muttered under her breath. The letter in her hands felt heavier than paper should, its contents potentially changing everything she thought she knew about her family's past. +``` +{% elif selected_pattern == "SHORT_DIALOGUE PROSE SHORT_DIALOGUE" %} +``` example +"Found it." Her flashlight beam illuminated ancient symbols carved deep into the stone wall. Centuries of dust coated everything in the buried chamber, and the air tasted of forgotten time on her tongue. "Incredible." +``` +{% elif selected_pattern == "PROSE DIALOGUE SHORT_DIALOGUE" %} +``` example +The library's silence was broken only by the soft whisper of pages turning under her fingers. Afternoon sunlight slanted through tall windows, casting long shadows between the towering bookshelves as she studied the ancient manuscript. "This changes everything," she breathed, hardly daring to believe what she was reading. "Incredible." +``` +{% elif selected_pattern == "DIALOGUE PROSE DIALOGUE" %} +``` example +"The coordinates match exactly," she announced, checking her instruments one final time. The ship rocked gently beneath her feet as fog began to roll in from the open sea, obscuring the horizon line she had been studying. "But I still think this is a mistake," she added, doubt creeping into her voice. +``` +{% elif selected_pattern == "SHORT_DIALOGUE PROSE PROSE" %} +``` example +"Ready." She stepped into the elevator as it descended into darkness, her equipment secured and her mind focused on the mission ahead. Twenty floors underground, the facility hummed with an energy that made the air itself feel electric against her skin. Somewhere in the distance, machinery operated with mechanical precision that spoke of purposes she was only beginning to understand. +``` +{% elif selected_pattern == "PROSE DIALOGUE PROSE DIALOGUE" %} +``` example +The garden party continued around her as if nothing had changed, but everything had. Laughter and champagne glasses created a perfect facade of normalcy that she had to maintain. "I need to find somewhere private," she whispered to herself, scanning the crowd for an escape route. The weight of what she had discovered pressed against her chest like a physical burden. "This could change everything," she murmured, her diplomatic smile never wavering despite the turmoil beneath. +``` +{% elif selected_pattern == "DIALOGUE PROSE PROSE" %} +``` example +"The test results came back positive," she said to the empty laboratory, setting down the folder with deliberate care. The silence stretched around her except for the gentle hum of machinery and the distant sound of traffic from the street below. Everything she had theorized, everything she had hoped to prove, was now undeniable reality staring back at her from printed charts and data. The implications would ripple through every aspect of her understanding, changing not just her research but potentially the course of her entire career. +``` +{% endif %} \ No newline at end of file diff --git a/src/talemate/prompts/templates/common/response-length.jinja2 b/src/talemate/prompts/templates/common/response-length.jinja2 index e00f6b8e..7e1b6a0b 100644 --- a/src/talemate/prompts/templates/common/response-length.jinja2 +++ b/src/talemate/prompts/templates/common/response-length.jinja2 @@ -1,9 +1,14 @@ {% if response_length -%} - {% if response_length <= 128 %}{% set response_length_units = "1 - 2 sentences" %} - {% elif response_length <= 256 %}{% set response_length_units = "2 - 4 sentences" %} - {% elif response_length <= 512 %}{% set response_length_units = "4 - 6 sentences" %} - {% elif response_length <= 1024 %}{% set response_length_units = "2 paragraphs" %} - {% else %}{% set response_length_units = "3 paragraphs" %} + {% if response_length <= 32 %}{% set response_length_units = "very few words" %} + {% elif response_length <= 64 %}{% set response_length_units = "1 - 2 sentences" %} + {% elif response_length <= 128 %}{% set response_length_units = "2 - 3 sentences" %} + {% elif response_length <= 256 %}{% set response_length_units = "3 - 5 sentences" %} + {% elif response_length <= 384 %}{% set response_length_units = "1 paragraph" %} + {% elif response_length <= 512 %}{% set response_length_units = "2 paragraphs" %} + {% elif response_length <= 1024 %}{% set response_length_units = "4 paragraphs" %} + {% else %}{% set response_length_units = "multiple paragraphs" %} {% endif%} {% endif -%} -{% if response_length_units -%}{{ prefix }}{{ response_length_units }}{{ suffix }}{% endif %} \ No newline at end of file +{% if response_length_units -%} +The length of your response must fit within {{ response_length_units }}. +{% endif %} \ No newline at end of file diff --git a/src/talemate/prompts/templates/conversation/dialogue-narrative.jinja2 b/src/talemate/prompts/templates/conversation/dialogue-narrative.jinja2 new file mode 100644 index 00000000..db05a959 --- /dev/null +++ b/src/talemate/prompts/templates/conversation/dialogue-narrative.jinja2 @@ -0,0 +1,80 @@ +{% extends "dialogue.jinja2" %} +{% block task_main_text %} +<|SECTION:TASK|> +You are writing a novel-style narrative continuation featuring {{ talking_character.name }} in a scene with {{ formatted_names }} in {{ scene.context }}. + +{% with exclude_phase_intent=false %}{% include "scene-intent.jinja2" %}{% endwith %} + +{% include "writing-style-instructions.jinja2" %} + +Your task is to write the next part of the story from {{ talking_character.name }}'s perspective, continuing the narrative in flowing, novel-like prose. + +{% set dialogue_examples -%} +{% for example in talking_character.random_dialogue_examples(scene, num=2, strip_name=True) -%} +``` example +{{ example }} +``` + +{% endfor -%} +{% endset %} + +## Writing Guidelines: + +**CRITICAL - Character Focus**: +- You are ONLY writing for {{ talking_character.name }} +- NEVER write dialogue for other characters +- NEVER describe other characters' actions, thoughts, or reactions +- NEVER make other characters speak or act +- Focus EXCLUSIVELY on {{ talking_character.name }}'s perspective, actions, thoughts, and words +- Other characters can exist in the scene but you cannot control them +- **ENVIRONMENTAL REACTIONS ARE ALLOWED**: You CAN describe how the environment or objects respond (e.g., "the door opened," "rain started," "the fire crackled") + +Really think about the above!!! + +**Character Goals**: {% if talking_character.sheet %}Consider {{ talking_character.name }}'s character sheet and any goals, motivations, or personality traits that should influence their actions and decisions.{% else %}Stay true to {{ talking_character.name }}'s established personality and motivations.{% endif %} + +**Narrative Style**: +- Write in clear, natural prose +- Integrate dialogue smoothly into the narrative +- Include relevant internal thoughts and emotions +- Show character motivations through actions and brief inner monologue +- Use concise, focused descriptions +- **AVOID PURPLE PROSE**: Keep descriptions practical and avoid overly flowery or elaborate language. Prefer simple, direct descriptions over ornate ones +- **BE CONCISE**: Don't over-describe scenes, emotions, or actions. A few well-chosen details are better than lengthy descriptions +- **CRITICAL - Tense and Perspective Consistency**: Examine the existing conversation history carefully and maintain the EXACT same tense (past/present) and perspective (first/second/third person) used in previous messages. If previous messages use third person past tense ("He walked"), continue with third person past tense. If they use first person present ("I walk"), continue with first person present. Do NOT switch styles mid-conversation. + +**Scene Progression - PRIORITIZE MOVING FORWARD**: Always advance the story. Don't just react - make things happen. Consider: +- What {{ talking_character.name }} wants to achieve in this moment +- How they would naturally respond to the current situation +- What actions or words would move the story forward meaningfully +- How to maintain continuity with previous events +- **TAKE ACTION**: Have {{ talking_character.name }} do something new, make a decision, or change the situation rather than just describing the current state + +**Avoid Repetition**: +- Don't repeat phrases, actions, or descriptions from recent messages +- Vary your sentence structure and vocabulary +- If {{ talking_character.name }} has already expressed similar thoughts or performed similar actions recently, find a fresh angle or new development +- Move the story forward rather than rehashing previous moments +- **Vary your opening patterns**: Avoid starting consecutive responses with similar sentence structures (e.g., "{{ talking_character.name }}'s [object]..." or "{{ talking_character.name }} [verbed]...") +- **Focus on different aspects**: If you've recently described equipment/tools, shift to emotions, environment, or internal thoughts instead + + +Based on {{ talking_character.name}}'s established dialogue patterns, maintain consistency with their voice and speaking style. +{% if dialogue_examples.strip() %} +{{ dialogue_examples.strip() }} +{%- else -%} +{% include "narrative-patterns.jinja2" %} +{%- endif %} + +{{ task_instructions }} + +Remember: Write clear, engaging prose that captures {{ talking_character.name }}'s experience in this moment. Focus on their perspective, thoughts, and actions while maintaining the natural flow of the story. Keep it concise and avoid unnecessary embellishment. + +**FINAL REMINDER**: You are {{ talking_character.name }}. Write ONLY what {{ talking_character.name }} thinks, says, and does. Do not write for any other character. + +<|CLOSE_SECTION|> +{% endblock -%} + +{% block response_scaffolding %}{{ bot_token }}{% if partial_message -%} +{{ partial_message.strip() }} +{% endif %}{% endblock -%} \ No newline at end of file diff --git a/src/talemate/prompts/templates/conversation/dialogue.jinja2 b/src/talemate/prompts/templates/conversation/dialogue.jinja2 index 2187d483..0cf89f04 100644 --- a/src/talemate/prompts/templates/conversation/dialogue.jinja2 +++ b/src/talemate/prompts/templates/conversation/dialogue.jinja2 @@ -29,7 +29,7 @@ {% if not director_guidance -%} {# INSERT ACTING INSTRUCTIONS VIA OFFSET #} {%- if actor_instructions_offset > 0 and talking_character.dialogue_instructions and scene.count_messages() > actor_instructions_offset -%} - {%- set _ = scene_history.insert(-actor_instructions_offset, "(Internal acting instructions for "+talking_character.name+": "+talking_character.dialogue_instructions+" "+actor_instructions+")") -%} + {%- set _ = scene_history.insert(-actor_instructions_offset, "(Broad character guidance for "+talking_character.name+": "+talking_character.dialogue_instructions+" "+actor_instructions+")") -%} {% endif -%} {% endif -%} {# END INSERT ACTING INSTRUCTIONS VIA OFFSET #} @@ -78,6 +78,8 @@ {{ task_main_text }} +<|RESPONSE_LENGTH_INSTRUCTIONS|> + {% if scene_context_length < large_context_threshold %}{{ acting_instructions }}{% endif %}{# if scene context is relatively short, its beneficial to move the acting instructions into the task #} {% with _text=scene_context_text %}{% include "internal-note-help.jinja2" %}{% endwith %} diff --git a/src/talemate/prompts/templates/creator/determine-content-context.jinja2 b/src/talemate/prompts/templates/creator/determine-content-context.jinja2 index eb7ca083..e523c60e 100644 --- a/src/talemate/prompts/templates/creator/determine-content-context.jinja2 +++ b/src/talemate/prompts/templates/creator/determine-content-context.jinja2 @@ -22,7 +22,7 @@ Your response should be "Content context: a ..." Examples: -{% for content_context in config.get('creator', {}).get('content_context',[]) -%} +{% for content_context in config.creator.content_context -%} - {{ content_context }} {% endfor -%} <|CLOSE_SECTION|> diff --git a/src/talemate/prompts/templates/director/cm-assign-voice.jinja2 b/src/talemate/prompts/templates/director/cm-assign-voice.jinja2 new file mode 100644 index 00000000..7117a17f --- /dev/null +++ b/src/talemate/prompts/templates/director/cm-assign-voice.jinja2 @@ -0,0 +1,59 @@ +{% set extra_context_content -%} +{% include "extra-context.jinja2" %} +{# scene analysis exists #}{% if scene.agent_state.summarizer and scene.agent_state.summarizer.scene_analysis %}{{ scene.agent_state.summarizer.scene_analysis }} {% endif %} +{% endset %} +{% set character_context_content -%} +<|SECTION:CHARACTER DETAILS - {{ character.name }}|> +{{ character.description }} + +{{ character.sheet }} +<|CLOSE_SECTION|> +{% endset %} +{% set voices_content -%} +<|SECTION:VOICES|> +{% for voice in voices %} +{ + "voice_id": "{{ voice.id }}", + "name": "{{ voice.label }}", + "tags": {{ voice.tags|tojson }}, + "used": {{ voice.used }} +} +{% endfor %} +<|CLOSE_SECTION|> +{% endset %} +{{ extra_context_content }} +{{ character_context_content }} +{{ voices_content }} +{% set context_tokens = count_tokens(extra_context_content) + count_tokens(character_context_content) + count_tokens(voices_content) %} +{% set budget=min(max_tokens-300-context_tokens, 1024) %} +{% with budget=budget %}{% include "scene-context.jinja2" %}{% endwith %} + +<|SECTION:FUNCTION CALLING INSTRUCTIONS|> +{{ focal.render_instructions() }} + +{{ + focal.callbacks.assign_voice.render( + "Assign a voice to "+character.name+". Use the tags and the character information to determine the best voice.", + voice_id="The voice id", + examples=[ + { + "voice_id": "kokoro:af_heart", + }, + { + "voice_id": "elevenlabs:wBXNqKUATyqu0RtYt25i", + } + ] + ) +}} +<|CLOSE_SECTION|> +<|SECTION:TASK|> +Assign a voice to {{ character.name }}. Use the tags and the character information to determine the best voice. + +First analyze the character and the available voices then make your choice by calling the `assign_voice` function. + +Prefer unused voices over used ones, but the most important thing is that the voice is a good fit for the character, so you can reuse voices if needed. + +{% if narrator_voice %}The narrator voice is {{ narrator_voice.id }}.{% endif %} + +You MUST call the `assign_voice` function. +<|CLOSE_SECTION|> \ No newline at end of file diff --git a/src/talemate/prompts/templates/director/guide-conversation.jinja2 b/src/talemate/prompts/templates/director/guide-conversation.jinja2 index e0960e3f..ddc76d38 100644 --- a/src/talemate/prompts/templates/director/guide-conversation.jinja2 +++ b/src/talemate/prompts/templates/director/guide-conversation.jinja2 @@ -76,13 +76,7 @@ Focus solely on WHAT needs to be conveyed. Trust the writer to capture {{ charac Finally ALWAYS briefly state the formatting guidelines: Speech MUST go inside "". -{% if response_length < 200 %}{% set num_sentences="1-2" -%} -{% elif response_length < 300 %}{% set num_sentences="3-4" -%} -{% elif response_length < 500 %}{% set num_sentences="4-5" -%} -{% elif response_length < 700 %}{% set num_sentences="6-7" -%} -{% elif response_length < 1000 %}{% set num_sentences="7-8" -%} -{% else %}{% set num_sentences="8-10" -%} -{% endif %}Fit your guidance within {{ num_sentences }} sentences. +{% include "response-length.jinja2" %} Use terse, direct language. Cut all unnecessary words. Be blunt and brief like scribbles on a notepad. @@ -90,4 +84,4 @@ Provide your response in the following format: GUIDANCE: ... your guidance for the story writer ... <|CLOSE_SECTION|> -{{ bot_token }} GUIDANCE: \ No newline at end of file +{{ bot_token }}GUIDANCE: \ No newline at end of file diff --git a/src/talemate/prompts/templates/director/guide-narration.jinja2 b/src/talemate/prompts/templates/director/guide-narration.jinja2 index 45a4a87f..6326c69f 100644 --- a/src/talemate/prompts/templates/director/guide-narration.jinja2 +++ b/src/talemate/prompts/templates/director/guide-narration.jinja2 @@ -64,13 +64,8 @@ Content Classification: {{ scene.context }} {% include "guide-narration-writing-style.jinja2" %} ### Rules for your instructions -{% if response_length < 200 %}{% set num_sentences="1-2" -%} -{% elif response_length < 300 %}{% set num_sentences="2-3" -%} -{% elif response_length < 500 %}{% set num_sentences="3-4" -%} -{% elif response_length < 700 %}{% set num_sentences="4-5" -%} -{% elif response_length < 1000 %}{% set num_sentences="6-7" -%} -{% else %}{% set num_sentences="7-8" -%} -{% endif %}Fit your guidance within {{ num_sentences }} sentences. + +{% include "response-length.jinja2" %} Use terse, direct language. Cut all unnecessary words. Be blunt and brief like scribbles on a notepad. @@ -78,4 +73,4 @@ Provide your response in the following format: GUIDANCE: ... your guidance for the narrator ... <|CLOSE_SECTION|> -{{ bot_token }} GUIDANCE: \ No newline at end of file +{{ bot_token }}GUIDANCE: \ No newline at end of file diff --git a/src/talemate/prompts/templates/director/scene-context.jinja2 b/src/talemate/prompts/templates/director/scene-context.jinja2 index 123582c0..5bbd5167 100644 --- a/src/talemate/prompts/templates/director/scene-context.jinja2 +++ b/src/talemate/prompts/templates/director/scene-context.jinja2 @@ -11,7 +11,6 @@ {{ scene_context }} {% endfor %} {% endset %} -{% endif %} {% with memory_prompt = history %}{% include "memory-context.jinja2" %}{% endwith %} <|SECTION:SCENE|> {{ scene_context_text }} diff --git a/src/talemate/prompts/templates/narrator/narrate-character-entry.jinja2 b/src/talemate/prompts/templates/narrator/narrate-character-entry.jinja2 index e316b0d7..fa28f77c 100644 --- a/src/talemate/prompts/templates/narrator/narrate-character-entry.jinja2 +++ b/src/talemate/prompts/templates/narrator/narrate-character-entry.jinja2 @@ -17,5 +17,5 @@ Narrate the entrance of {{ character.name }} into the scene. {% if not narrative {% include "narrative-direction.jinja2" %} -Write 2 to 4 sentences. {{ extra_instructions }} +{{ extra_instructions }} <|CLOSE_SECTION|> \ No newline at end of file diff --git a/src/talemate/prompts/templates/narrator/narrate-character-exit.jinja2 b/src/talemate/prompts/templates/narrator/narrate-character-exit.jinja2 index 0c454c3c..3704c580 100644 --- a/src/talemate/prompts/templates/narrator/narrate-character-exit.jinja2 +++ b/src/talemate/prompts/templates/narrator/narrate-character-exit.jinja2 @@ -17,5 +17,5 @@ Narrate the exit of {{ character.name }} from the scene.{% if not narrative_dire {% include "narrative-direction.jinja2" %} -Write 2 to 4 sentences. {{ extra_instructions }} +{{ extra_instructions }} <|CLOSE_SECTION|> \ No newline at end of file diff --git a/src/talemate/prompts/templates/narrator/narrate-query.jinja2 b/src/talemate/prompts/templates/narrator/narrate-query.jinja2 index d4f4ac5e..0b2cc7d1 100644 --- a/src/talemate/prompts/templates/narrator/narrate-query.jinja2 +++ b/src/talemate/prompts/templates/narrator/narrate-query.jinja2 @@ -25,7 +25,6 @@ Use the established context to inform your responses, anchoring them to final li Provide information that maintains continuity with everything up to and including the final line. Respond as an omniscient, all-seeing narrator with deep knowledge of the story world. -Respond with 1-2 sentences of concise narration fitting the scene's context. Focus on descriptive prose and implied experiences. Embody the narrator's role completely, using a unique narrative voice. diff --git a/src/talemate/prompts/templates/narrator/narrate-time-passage.jinja2 b/src/talemate/prompts/templates/narrator/narrate-time-passage.jinja2 index d43f7247..95799723 100644 --- a/src/talemate/prompts/templates/narrator/narrate-time-passage.jinja2 +++ b/src/talemate/prompts/templates/narrator/narrate-time-passage.jinja2 @@ -7,6 +7,6 @@ Narrate the passage of time that just occured, subtly move the story forward, and set up the next scene. Your main goal is to fill in what happened during the time passage. {% include "narrative-direction.jinja2" %} -Write 2 to 4 sentences. {{ extra_instructions }} +{{ extra_instructions }} <|CLOSE_SECTION|> {{ bot_token }}{{ time_passed }}: \ No newline at end of file diff --git a/src/talemate/prompts/templates/narrator/narrative-direction.jinja2 b/src/talemate/prompts/templates/narrator/narrative-direction.jinja2 index 482d13d6..d21b630d 100644 --- a/src/talemate/prompts/templates/narrator/narrative-direction.jinja2 +++ b/src/talemate/prompts/templates/narrator/narrative-direction.jinja2 @@ -16,8 +16,7 @@ directions. {% else %} Maintain an informal, conversational tone. {# writing style and guidance END #}{% endif %} -{# scene analysis exists #}{% if agent_context_state["summarizer__scene_analysis"] %}Use the scene analysis to help -ground your narration.{% endif %} +{# scene analysis exists #}{% if agent_context_state["summarizer__scene_analysis"] %}Use the scene analysis to help ground your narration.{% endif %} {# context investigation exists #}{% if agent_context_state["summarizer__context_investigation"] %}Use the historical context to help ground your narration.{% endif %} {# regenerate-context #} \ No newline at end of file diff --git a/src/talemate/prompts/templates/summarizer/analyze-scene-for-next-conversation.jinja2 b/src/talemate/prompts/templates/summarizer/analyze-scene-for-next-conversation.jinja2 index 4c7e40a3..976a4d72 100644 --- a/src/talemate/prompts/templates/summarizer/analyze-scene-for-next-conversation.jinja2 +++ b/src/talemate/prompts/templates/summarizer/analyze-scene-for-next-conversation.jinja2 @@ -46,8 +46,6 @@ The information you write will be given to the other story editors to write {{ c Note that the 'Potentially relevant information' section has been filled in from a previous prompt and may not be relevant at all. {% endif %} -{% if length >= 1024 %}{{ li() }}. Carefully plan {{ character.name }}'s next action, taking your analysis into account. This must be a short list of instructions and considerations, not narrative text. (Aim for 2 - 3 items){% endif %} - {% if length <= 256 %}Your analysis should be 1 - 2 paragraphs long.{% elif length <= 512 %}Your analysis should be 2 - 3 paragraphs long.{% endif %} {% if length <= 512 %}Use terse, direct language. Cut all unnecessary words. Be blunt and brief like scribbles on a notepad.{% endif %} diff --git a/src/talemate/prompts/templates/summarizer/markup-context-for-tts.jinja2 b/src/talemate/prompts/templates/summarizer/markup-context-for-tts.jinja2 new file mode 100644 index 00000000..02f7085e --- /dev/null +++ b/src/talemate/prompts/templates/summarizer/markup-context-for-tts.jinja2 @@ -0,0 +1,135 @@ +<|SECTION:CHARACTERS|> +{% for character in scene.characters -%} +### {{ character.name }} +{{ character.description }} + +{% endfor %} +<|CLOSE_SECTION|> +{% include "content-classification.jinja2" %} + +<|SECTION:SCENE|> +{% include "extra-context.jinja2" -%} + +{% set scene_context = scene.context_history( + budget=1500, + min_dialogue=10, + sections=False, + keep_director=False, + chapter_labels=False + ) +-%} +{% for scene_line in scene_context -%} +{{ scene_line }} + +{% endfor %} +<|CLOSE_SECTION|> +<|SECTION:TASK|> +Your task is to break down the text into dialogue and narration, identifying the speaker for each line. + +Dialogue is any text that is between double quotes. Each piece of dialogue and narration should be on its own line, prefixed with the speaker name in square brackets. + +Use [Narrator] for ALL exposition and narrative text - this includes action descriptions, dialogue tags (like "he said"), character thoughts, and any text that is not spoken dialogue. +Use [Speaker Name] for dialogue ONLY, with normal capitalization (e.g., [John], not [JOHN]). + +Your response should contain: +1. First, an section with your reasoning about who is speaking each line +2. Then, a section with the text broken down line by line with speaker tags +<|CLOSE_SECTION|> +<|SECTION:EXAMPLES|> + +He stopped at the door and looked back at Jasmine. "I will be back soon." + + +First part is narration describing actions. The dialogue is spoken by "he" which from context refers to John. + + +[Narrator] He stopped at the door and looked back at Jasmine. +[John] I will be back soon. + + + +"Are you coming?" Mary asked. David shook his head. "Not today." + + +First quote is explicitly attributed to Mary through "Mary asked." Middle section is narration (including "Mary asked" and David's action). David speaks the second quote as indicated by the preceding action "David shook his head." + + +[Mary] Are you coming? +[Narrator] Mary asked. David shook his head. +[David] Not today. + + + +Sarah rushed into the room. "You'll never believe what happened!" + +Mike looked up from his book. "What now?" He set it aside. "Did you get the promotion?" + +"Even better!" She pulled out her phone. "I won that contest - first place!" + +"The photography one?" Mike leaned forward. "That's amazing!" + + +All non-quoted text is narration, regardless of whether it describes actions, thoughts, or dialogue attribution. Only the actual quoted speech is assigned to characters. + + +[Narrator] Sarah rushed into the room. +[Sarah] You'll never believe what happened! +[Narrator] Mike looked up from his book. +[Mike] What now? +[Narrator] He set it aside. +[Mike] Did you get the promotion? +[Sarah] Even better! +[Narrator] She pulled out her phone. +[Sarah] I won that contest - first place! +[Mike] The photography one? +[Narrator] Mike leaned forward. +[Mike] That's amazing! + + + +"Listen," Tom said, his voice low. "I need to tell you something." He glanced around nervously. "But you can't tell anyone, okay?" His hands trembled as he continued. "I saw what happened that night." + + +All dialogue is Tom's - first quote has explicit attribution "Tom said," and subsequent pronouns "He" refer back to Tom. All descriptive text including "Tom said, his voice low" is narration. + + +[Tom] Listen, +[Narrator] Tom said, his voice low. +[Tom] I need to tell you something. +[Narrator] He glanced around nervously. +[Tom] But you can't tell anyone, okay? +[Narrator] His hands trembled as he continued. +[Tom] I saw what happened that night. + + + +Emma burst out laughing. "You actually believed him?" She wiped tears from her eyes. "Oh, that's priceless." + +"I don't see what's so funny," Marcus replied stiffly. He paused, then sighed. "Fine, maybe I was a bit naive." + + +All action descriptions and dialogue tags are narration. Only the quoted speech is attributed to the characters. + + +[Narrator] Emma burst out laughing. +[Emma] You actually believed him? +[Narrator] She wiped tears from her eyes. +[Emma] Oh, that's priceless. +[Marcus] I don't see what's so funny, +[Narrator] Marcus replied stiffly. He paused, then sighed. +[Marcus] Fine, maybe I was a bit naive. + +<|CLOSE_SECTION|> +<|SECTION:GUIDELINES|> +- Break down ALL text into separate lines for dialogue and narration +- Each line should start with [Speaker Name] or [Narrator] +- CRITICAL: Only actual spoken dialogue (text within quotes) should be attributed to characters +- EVERYTHING else is [Narrator]: action descriptions, dialogue tags ("he said"), thoughts, scene setting, etc. +- Use proper name casing (John, not JOHN) +- Use [Unknown] if speaker cannot be determined +- Do not include quotation marks in the dialogue lines +- Preserve all text exactly - only reorganize into the new format +<|CLOSE_SECTION|> +<|SECTION:TEXT|> +{{ text }} +{{ set_prepared_response("\n") }} \ No newline at end of file diff --git a/src/talemate/prompts/templates/world_state/analyze-history-and-follow-instructions.jinja2 b/src/talemate/prompts/templates/world_state/analyze-history-and-follow-instructions.jinja2 index e608eda1..a445e3be 100644 --- a/src/talemate/prompts/templates/world_state/analyze-history-and-follow-instructions.jinja2 +++ b/src/talemate/prompts/templates/world_state/analyze-history-and-follow-instructions.jinja2 @@ -16,8 +16,4 @@ Begin by always grounding your answer with a location, event and time, if possible. -{% if response_length < 512 %} -Your response should be 1 to 3 sentences long. Keep it concise but informative. -{% elif response_length < 1024 %} -Your response should be 2 to 4 sentences long. -{% endif %} \ No newline at end of file +{% include "response-length.jinja2" %} \ No newline at end of file diff --git a/src/talemate/prompts/templates/world_state/determine-content-context.jinja2 b/src/talemate/prompts/templates/world_state/determine-content-context.jinja2 index 565ece71..d75f0b4c 100644 --- a/src/talemate/prompts/templates/world_state/determine-content-context.jinja2 +++ b/src/talemate/prompts/templates/world_state/determine-content-context.jinja2 @@ -10,7 +10,7 @@ The content context should be a single short phrase classification that describe Choices: -{% for content_context in config.get('creator', {}).get('content_context',[]) -%} +{% for content_context in config.creator.content_context -%} - {{ content_context }} {% endfor -%} {% for content_context in extra_choices -%} diff --git a/src/talemate/prompts/templates/world_state/request-world-state-v2.jinja2 b/src/talemate/prompts/templates/world_state/request-world-state-v2.jinja2 index 597f2ad5..1d46e436 100644 --- a/src/talemate/prompts/templates/world_state/request-world-state-v2.jinja2 +++ b/src/talemate/prompts/templates/world_state/request-world-state-v2.jinja2 @@ -52,7 +52,7 @@ No dialogue so far {% endif -%} <|CLOSE_SECTION|> <|SECTION:TASK|> -Create a JSON object for the world state that reflects the scene progression so far. +Create {{ data_format_type() }} data for the world state that reflects the scene progression so far. The world state needs to include important concrete and material items present in the scene during line {{ final_line_number }}. The world state needs to include persons (characters) interacting during line {{ final_line_number }}. @@ -69,6 +69,8 @@ characters should have the following attributes: `emotion`, `snapshot` items should have the following attributes: `snapshot` item keys must be reader friendly, so "Item name" instead of "item_name". +Don't overthink it. + <|CLOSE_SECTION|> <|SECTION:UPDATED WORLD STATE|> {{ set_data_response(dict(characters={"name":{}}), cutoff=3) }} \ No newline at end of file diff --git a/src/talemate/prompts/templates/world_state/update-reinforcements.jinja2 b/src/talemate/prompts/templates/world_state/update-reinforcements.jinja2 index c8a84b3a..f9350593 100644 --- a/src/talemate/prompts/templates/world_state/update-reinforcements.jinja2 +++ b/src/talemate/prompts/templates/world_state/update-reinforcements.jinja2 @@ -26,6 +26,9 @@ No dialogue so far {% endif -%} <|CLOSE_SECTION|> + +{% include "writing-style-instructions.jinja2" %} + <|SECTION:TASK|> {# QUESTION #} {%- if question.strip()[-1] == '?' %} @@ -49,7 +52,7 @@ YOUR ANSWER IS CONFIDENT, MAKE CREATIVE CHOICES AS NEEDED. {% if instructions %} {{ instructions }} {% endif %} -The tone of your answer should be consistent with the tone of the story so far. +The tone of your answer must be consistent with the tone of the story so far. Question: {{ question }} (At line {{ final_line_number }} in the scene progression) {% if answer %}Previous Answer: {{ answer }} @@ -77,7 +80,7 @@ YOUR ANSWER IS CONFIDENT, MAKE CREATIVE CHOICES AS NEEDED. {% if instructions %} {{ instructions }} {% endif %} -The tone of your answer should be consistent with the tone of the story so far. +The tone of your answer must be consistent with the tone of the story so far. {% if answer %}Previous Value: {{ answer }} {% endif-%} diff --git a/src/talemate/scene_assets.py b/src/talemate/scene_assets.py index 3ca25bbf..2ebd45b8 100644 --- a/src/talemate/scene_assets.py +++ b/src/talemate/scene_assets.py @@ -40,7 +40,7 @@ class SceneAssets: self.cover_image = None @property - def asset_directory(self): + def asset_directory(self) -> str: """ Returns the scene's asset path """ diff --git a/src/talemate/scene_message.py b/src/talemate/scene_message.py index 40132655..3883e103 100644 --- a/src/talemate/scene_message.py +++ b/src/talemate/scene_message.py @@ -36,8 +36,8 @@ class Flags(enum.IntFlag): Flags for messages """ - NONE = 0 - HIDDEN = 1 + NONE = 0x0 + HIDDEN = 0x1 @dataclass @@ -146,6 +146,8 @@ class SceneMessage: def as_format(self, format: str, **kwargs) -> str: if format == "movie_script": return self.message.rstrip("\n") + "\n" + elif format == "narrative": + return self.message.strip() return self.message def set_source(self, agent: str, function: str, **kwargs): @@ -218,6 +220,8 @@ class CharacterMessage(SceneMessage): def as_format(self, format: str, **kwargs) -> str: if format == "movie_script": return self.as_movie_script + elif format == "narrative": + return self.without_name.strip() return self.message @@ -267,6 +271,7 @@ class DirectorMessage(SceneMessage): action: str = "actor_instruction" source: str = "ai" typ = "director" + subtype: str | None = None @property def character_name(self) -> str: @@ -343,7 +348,7 @@ class DirectorMessage(SceneMessage): return "" mode = kwargs.get("mode", "direction") - if format == "movie_script": + if format in ["movie_script", "narrative"]: if mode == "internal_monologue": return f"\n({self.as_inner_monologue})\n" else: @@ -384,7 +389,7 @@ class ReinforcementMessage(SceneMessage): return f"# Internal note for {self.character_name} - {self.question}\n{self.message}" def as_format(self, format: str, **kwargs) -> str: - if format == "movie_script": + if format in ["movie_script", "narrative"]: message = str(self)[2:] return f"\n({message})\n" return f"\n{self.message}\n" @@ -452,7 +457,7 @@ class ContextInvestigationMessage(SceneMessage): return rv def as_format(self, format: str, **kwargs) -> str: - if format == "movie_script": + if format in ["movie_script", "narrative"]: message = str(self)[2:] return f"\n({message})\n".replace("*", "") return f"\n{self.message}\n".replace("*", "") diff --git a/src/talemate/server/api.py b/src/talemate/server/api.py index 66f85c88..39e4afc2 100644 --- a/src/talemate/server/api.py +++ b/src/talemate/server/api.py @@ -8,7 +8,7 @@ import websockets import talemate.instance as instance from talemate import VERSION -from talemate.config import load_config +from talemate.config import get_config, Config, commit_config, update_config from talemate.client.system_prompts import RENDER_CACHE as SYSTEM_PROMPTS_CACHE from talemate.server.websocket_server import WebsocketHandler from talemate.util.data import JSONEncoder @@ -18,8 +18,34 @@ from talemate.game.engine.nodes.registry import import_initial_node_definitions log = structlog.get_logger("talemate") +# Only one active frontend websocket is allowed at a time. +# We keep a reference to the active websocket here and reject subsequent +# connection attempts while it is still open. +_active_frontend_websocket = None + async def websocket_endpoint(websocket): + global _active_frontend_websocket + + # Reject the connection if another frontend is already connected. + if _active_frontend_websocket is not None: + try: + await websocket.send( + json.dumps( + { + "type": "system", + "status": "error", + "message": "Another Talemate frontend is already connected. Only one connection is allowed.", + } + ) + ) + finally: + # Close the websocket with a normal closure code. + await websocket.close() + return + + # Mark this websocket as the active frontend connection. + _active_frontend_websocket = websocket # Create a queue for outgoing messages message_queue = asyncio.Queue() handler = WebsocketHandler(websocket, message_queue) @@ -30,6 +56,7 @@ async def websocket_endpoint(websocket): import_initial_node_definitions() async def frontend_disconnect(exc): + global _active_frontend_websocket nonlocal scene_task log.warning(f"frontend disconnected: {exc}") @@ -44,6 +71,9 @@ async def websocket_endpoint(websocket): handler.scene.continue_scene = False if scene_task: scene_task.cancel() + # Clear the active websocket reference so a new frontend can connect. + if _active_frontend_websocket is websocket: + _active_frontend_websocket = None # Create a task to send messages from the queue async def send_messages(): @@ -61,6 +91,7 @@ async def websocket_endpoint(websocket): while True: await instance.emit_clients_status() await instance.agent_ready_checks() + await commit_config() await asyncio.sleep(3) # create a task that will retriece client boostrap information @@ -130,6 +161,7 @@ async def websocket_endpoint(websocket): }, } ) + instance.emit_agents_status() if scene_data and filename: file_path = handler.handle_character_card_upload( @@ -156,9 +188,14 @@ async def websocket_endpoint(websocket): query = data.get("query", "") handler.request_scenes_list(query) elif action_type == "configure_clients": - await handler.configure_clients(data.get("clients")) + await update_config({"clients": data.get("clients")}) + await instance.instantiate_clients() + await instance.purge_clients() + await instance.emit_clients_status() + await instance.ensure_agent_llm_client() elif action_type == "configure_agents": - await handler.configure_agents(data.get("agents")) + await update_config({"agents": data.get("agents")}) + await instance.configure_agents() elif action_type == "request_client_status": await handler.request_client_status() elif action_type == "delete_message": @@ -184,7 +221,7 @@ async def websocket_endpoint(websocket): elif action_type == "request_app_config": log.info("request_app_config") - config = load_config() + config: Config = get_config().model_dump() config.update(system_prompt_defaults=SYSTEM_PROMPTS_CACHE) await message_queue.put( diff --git a/src/talemate/server/assistant.py b/src/talemate/server/assistant.py index 1abd187d..4f7860ee 100644 --- a/src/talemate/server/assistant.py +++ b/src/talemate/server/assistant.py @@ -5,6 +5,7 @@ import traceback from talemate.agents.creator.assistant import ContentGenerationContext from talemate.emit import emit from talemate.instance import get_agent +from talemate.server.websocket_plugin import Plugin log = structlog.get_logger("talemate.server.assistant") @@ -14,26 +15,12 @@ class ForkScenePayload(pydantic.BaseModel): save_name: str | None = None -class AssistantPlugin: +class AssistantPlugin(Plugin): router = "assistant" - @property - def scene(self): - return self.websocket_handler.scene - def __init__(self, websocket_handler): self.websocket_handler = websocket_handler - async def handle(self, data: dict): - log.info("assistant action", action=data.get("action")) - - fn = getattr(self, f"handle_{data.get('action')}", None) - - if fn is None: - return - - await fn(data) - async def handle_contextual_generate(self, data: dict): payload = ContentGenerationContext(**data) creator = get_agent("creator") @@ -61,7 +48,7 @@ class AssistantPlugin: async def handle_autocomplete(self, data: dict): data = ContentGenerationContext(**data) try: - creator = self.scene.get_helper("creator").agent + creator = get_agent("creator") context_type, context_name = data.computed_context if context_type == "dialogue": diff --git a/src/talemate/server/config.py b/src/talemate/server/config.py index d368c2a5..5a8cdda6 100644 --- a/src/talemate/server/config.py +++ b/src/talemate/server/config.py @@ -5,8 +5,9 @@ import os from talemate import VERSION from talemate.client.model_prompts import model_prompt from talemate.client.registry import CLIENT_CLASSES +from talemate.client.base import ClientBase from talemate.config import Config as AppConfigData -from talemate.config import load_config, save_config +from talemate.config import get_config, Config, update_config from talemate.emit import emit from talemate.instance import emit_clients_status, get_client @@ -60,15 +61,13 @@ class ConfigPlugin: async def handle_save(self, data): app_config_data = ConfigPayload(**data) - current_config = load_config() - - current_config.update(app_config_data.dict().get("config")) - - save_config(current_config) - - self.websocket_handler.config = current_config + await update_config(app_config_data.config.model_dump()) self.websocket_handler.queue_put( - {"type": "app_config", "data": load_config(), "version": VERSION} + { + "type": "app_config", + "data": get_config().model_dump(), + "version": VERSION, + } ) self.websocket_handler.queue_put( { @@ -82,20 +81,18 @@ class ConfigPlugin: payload = DefaultCharacterPayload(**data["data"]) - current_config = load_config() - - current_config["game"]["default_player_character"] = payload.model_dump() + config: Config = get_config() + config.game.default_player_character = payload.model_dump() log.info( "Saving default character", - character=current_config["game"]["default_player_character"], + character=config.game.default_player_character, ) - save_config(current_config) + await config.set_dirty() - self.websocket_handler.config = current_config self.websocket_handler.queue_put( - {"type": "app_config", "data": load_config(), "version": VERSION} + {"type": "app_config", "data": config.model_dump(), "version": VERSION} ) self.websocket_handler.queue_put( { @@ -204,9 +201,15 @@ class ConfigPlugin: payload = ToggleClientPayload(**data) log.info("Toggling client", name=payload.name, state=payload.state) - client = get_client(payload.name) + client: ClientBase = get_client(payload.name) - client.enabled = payload.state + current_state = client.enabled + + if current_state != payload.state: + if not payload.state: + await client.disable() + else: + await client.enable() self.websocket_handler.queue_put( { @@ -226,13 +229,13 @@ class ConfigPlugin: log.info("Removing scene from recents", path=payload.path) - current_config = load_config(as_model=True) + config: Config = get_config() - for recent_scene in list(current_config.recent_scenes.scenes): + for recent_scene in list(config.recent_scenes.scenes): if recent_scene.path == payload.path: - current_config.recent_scenes.scenes.remove(recent_scene) + config.recent_scenes.scenes.remove(recent_scene) - save_config(current_config) + await config.set_dirty() self.websocket_handler.queue_put( { @@ -245,12 +248,14 @@ class ConfigPlugin: ) self.websocket_handler.queue_put( - {"type": "app_config", "data": load_config(), "version": VERSION} + {"type": "app_config", "data": config.model_dump(), "version": VERSION} ) async def handle_delete_scene(self, data): payload = DeleteScenePayload(**data) + await self.handle_remove_scene_from_recents(data) + log.info("Deleting scene", path=payload.path) # remove the file @@ -269,6 +274,8 @@ class ConfigPlugin: } ) + config: Config = get_config() + self.websocket_handler.queue_put( - {"type": "app_config", "data": load_config(), "version": VERSION} + {"type": "app_config", "data": config.model_dump(), "version": VERSION} ) diff --git a/src/talemate/server/devtools.py b/src/talemate/server/devtools.py index 3bc17ffc..4c3d93bc 100644 --- a/src/talemate/server/devtools.py +++ b/src/talemate/server/devtools.py @@ -1,5 +1,7 @@ import pydantic import structlog +from talemate.instance import get_client +from talemate.client.base import ClientBase from talemate.scene.state_editor import SceneStateEditor from talemate.scene.schema import SceneState from talemate.server.websocket_plugin import Plugin @@ -39,7 +41,7 @@ class DevToolsPlugin(Plugin): async def handle_test_prompt(self, data): payload = TestPromptPayload(**data) - client = self.websocket_handler.llm_clients[payload.client_name]["client"] + client: ClientBase = get_client(payload.client_name) log.info( "Testing prompt", @@ -66,6 +68,7 @@ class DevToolsPlugin(Plugin): "client_name": payload.client_name, "kind": payload.kind, "response": response, + "reasoning": client.reasoning_response, }, } ) diff --git a/src/talemate/server/quick_settings.py b/src/talemate/server/quick_settings.py index 10784fda..0e58ac97 100644 --- a/src/talemate/server/quick_settings.py +++ b/src/talemate/server/quick_settings.py @@ -3,7 +3,7 @@ from typing import Any import pydantic import structlog -from talemate.config import save_config +from talemate.config import get_config, Config log = structlog.get_logger("talemate.server.quick_settings") @@ -35,15 +35,16 @@ class QuickSettingsPlugin: async def handle_set(self, data: dict): payload = SetQuickSettingsPayload(**data) + config: Config = get_config() if payload.setting == "auto_save": - self.scene.config["game"]["general"]["auto_save"] = payload.value + config.game.general.auto_save = payload.value elif payload.setting == "auto_progress": - self.scene.config["game"]["general"]["auto_progress"] = payload.value + config.game.general.auto_progress = payload.value else: raise NotImplementedError(f"Setting {payload.setting} not implemented.") - save_config(self.scene.config) + await config.set_dirty() self.websocket_handler.queue_put( {"type": self.router, "action": "set_done", "data": payload.model_dump()} diff --git a/src/talemate/server/run.py b/src/talemate/server/run.py index 712d7498..6f9694e4 100644 --- a/src/talemate/server/run.py +++ b/src/talemate/server/run.py @@ -1,5 +1,8 @@ print("Talemate starting.") -print("Startup may take a moment to download some dependencies, please be patient ...") +print("Startup may take a moment to initialize some dependencies, please be patient...") +import time + +t_import_start = time.perf_counter() import os import logging @@ -12,9 +15,11 @@ import sys import websockets -from talemate.server.api import websocket_endpoint +import talemate.config # noqa: F401 from talemate.version import VERSION +print("Initialization time", time.perf_counter() - t_import_start) + TALEMATE_DEBUG = os.environ.get("TALEMATE_DEBUG", "0") log_level = logging.DEBUG if TALEMATE_DEBUG == "1" else logging.INFO @@ -115,6 +120,7 @@ def run_server(args): :param args: command line arguments parsed by argparse """ + import talemate.client.registry import talemate.agents.custom import talemate.client.custom import talemate.agents @@ -123,9 +129,13 @@ def run_server(args): from talemate.prompts.overrides import get_template_overrides import talemate.client.system_prompts as system_prompts from talemate.emit.base import emit + import talemate.agents.tts.voice_library as voice_library # import node libraries import talemate.game.engine.nodes.load_definitions + import talemate.config + import talemate.instance + from talemate.server.api import websocket_endpoint config = talemate.config.cleanup() @@ -150,6 +160,10 @@ def run_server(args): # Get (or create) the asyncio event loop loop = asyncio.get_event_loop() + loop.run_until_complete(voice_library.require_instance()) + loop.run_until_complete(talemate.instance.instantiate_clients()) + loop.run_until_complete(talemate.instance.instantiate_agents()) + # websockets>=12 requires ``websockets.serve`` to be called from within a # running event-loop (it uses ``asyncio.get_running_loop()`` internally). # Calling it directly, before the loop is running, raises @@ -180,7 +194,7 @@ def run_server(args): frontend_task = None log.info("talemate backend started", host=args.host, port=args.port) - emit("talemate_started", data=config.model_dump()) + emit("talemate_started", data={"config": config}) try: loop.run_forever() diff --git a/src/talemate/server/websocket_plugin.py b/src/talemate/server/websocket_plugin.py index 86cf5943..60bd5ecc 100644 --- a/src/talemate/server/websocket_plugin.py +++ b/src/talemate/server/websocket_plugin.py @@ -1,5 +1,10 @@ import structlog +from typing import TYPE_CHECKING from talemate.emit import emit +import traceback + +if TYPE_CHECKING: + from talemate.tale_mate import Scene __all__ = [ "Plugin", @@ -9,10 +14,10 @@ log = structlog.get_logger("talemate.server.visual") class Plugin: - router = "router" + router: str = "router" @property - def scene(self): + def scene(self) -> "Scene | None": return self.websocket_handler.scene def __init__(self, websocket_handler): @@ -58,4 +63,14 @@ class Plugin: if fn is None: return - await fn(data) + try: + await fn(data) + except Exception as e: + action_name = data.get("action") + log.error( + "Error handling action", + action=action_name, + error=e, + traceback=traceback.format_exc(), + ) + await self.signal_operation_failed(f"Error during {action_name}: {e}") diff --git a/src/talemate/server/websocket_server.py b/src/talemate/server/websocket_server.py index f6de03a7..ab1149d1 100644 --- a/src/talemate/server/websocket_server.py +++ b/src/talemate/server/websocket_server.py @@ -6,13 +6,13 @@ import traceback import structlog import talemate.instance as instance -from talemate import Helper, Scene -from talemate.client.base import ClientBase -from talemate.client.registry import CLIENT_CLASSES +from talemate import Scene from talemate.client.system_prompts import RENDER_CACHE as SYSTEM_PROMPTS_CACHE -from talemate.config import SceneAssetUpload, load_config, save_config +from talemate.config.schema import SceneAssetUpload +from talemate.config import get_config, Config from talemate.context import ActiveScene from talemate.emit import Emission, Receiver, abort_wait_for_input, emit +import talemate.emit.async_signals as async_signals from talemate.files import list_scenes_directory from talemate.load import load_scene from talemate.scene_assets import Asset @@ -39,20 +39,11 @@ AGENT_INSTANCES = {} class WebsocketHandler(Receiver): def __init__(self, socket, out_queue, llm_clients=dict()): - self.agents = {typ: {"name": typ} for typ in instance.agent_types()} self.socket = socket self.waiting_for_input = False self.input = None self.scene = Scene() self.out_queue = out_queue - self.config = load_config() - - for name, agent_config in self.config.get("agents", {}).items(): - self.agents[name] = agent_config - - self.llm_clients = self.config.get("clients", llm_clients) - - instance.get_agent("memory", self.scene) self.routes = { assistant.AssistantPlugin.router: assistant.AssistantPlugin(self), @@ -77,15 +68,13 @@ class WebsocketHandler(Receiver): # to connect signals handlers to the websocket handler self.connect() - # connect LLM clients - loop = asyncio.get_event_loop() - loop.run_until_complete(self.connect_llm_clients()) - self.set_agent_routers() - # self.request_scenes_list() + instance.emit_agents_status() - # instance.emit_clients_status() + @property + def config(self) -> Config: + return get_config() def set_agent_routers(self): for agent_type, agent in instance.AGENTS.items(): @@ -110,89 +99,22 @@ class WebsocketHandler(Receiver): if hasattr(plugin, "disconnect"): plugin.disconnect() - async def connect_llm_clients(self): - client = None - - for client_name, client_config in self.llm_clients.items(): - try: - client = self.llm_clients[client_name]["client"] = instance.get_client( - **client_config - ) - except TypeError as e: - raise - log.error("Error connecting to client", client_name=client_name, e=e) - continue - - log.info( - "Configured client", - client_name=client_name, - client_type=client.client_type, - ) - - await self.connect_agents() - - async def connect_agents(self): - if not self.llm_clients: - instance.emit_agents_status() - return - - self.set_agent_routers() - - for agent_typ, agent_config in self.agents.items(): - try: - client = self.llm_clients.get(agent_config.get("client"))["client"] - except TypeError: - client = None - - if not client or not client.enabled: - # select first enabled client - try: - client = self.get_first_enabled_client() - agent_config["client"] = client.name - except IndexError: - client = None - - if not client: - agent_config["client"] = None - - if client: - log.debug("Linked agent", agent_typ=agent_typ, client=client.name) - else: - log.warning("No client available for agent", agent_typ=agent_typ) - - agent = instance.get_agent(agent_typ, client=client) - agent.client = client - await agent.apply_config(**agent_config) - - instance.emit_agents_status() - - def get_first_enabled_client(self) -> ClientBase: - """ - Will return the first enabled client available - - If no enabled clients are available, an IndexError will be raised - """ - for client in self.llm_clients.values(): - if client and client["client"].enabled: - return client["client"] - raise IndexError("No enabled clients available") + def connect(self): + super().connect() + async_signals.get("config.changed").connect(self.on_config_changed) def init_scene(self): # Setup scene scene = Scene() # Init helper agents - for agent_typ, agent_config in self.agents.items(): + for agent_typ in instance.agent_types(): + agent = instance.get_agent(agent_typ) + agent.connect(scene) + agent.scene = scene if agent_typ == "memory": - agent_config["scene"] = scene - - log.debug("init agent", agent_typ=agent_typ, agent_config=agent_config) - agent = instance.get_agent(agent_typ, **agent_config) - - # if getattr(agent, "client", None): - # self.llm_clients[agent.client.name] = agent.client - - scene.add_helper(Helper(agent)) + continue + log.debug("init agent", agent_typ=agent_typ) return scene @@ -211,8 +133,6 @@ class WebsocketHandler(Receiver): await asyncio.sleep(0.1) return - conversation_helper = scene.get_helper("conversation") - scene.active = True with ActiveScene(scene): @@ -220,7 +140,6 @@ class WebsocketHandler(Receiver): scene = await load_scene( scene, path_or_data, - conversation_helper.agent.client, reset=reset, ) except MemoryAgentError as e: @@ -246,145 +165,6 @@ class WebsocketHandler(Receiver): # Schedule the put coroutine to run as soon as possible loop.call_soon_threadsafe(lambda: self.out_queue.put_nowait(data)) - async def configure_clients(self, clients): - existing = set(self.llm_clients.keys()) - - self.llm_clients = {} - - # log.info("Configuring clients", clients=clients) - - for client in clients: - client.pop("status", None) - client_cls = CLIENT_CLASSES.get(client["type"]) - - # so hacky, such sad - ignore_model_names = [ - "Disabled", - "No model loaded", - "Could not connect", - "No API key set", - ] - if client.get("model") in ignore_model_names: - # if client instance exists copy model_name from it - _client = instance.get_client(client["name"]) - if _client: - client["model"] = getattr(_client, "model_name", None) - else: - client.pop("model", None) - - if not client_cls: - log.error("Client type not found", client=client) - continue - - client_config = self.llm_clients[client["name"]] = { - "name": client["name"], - "type": client["type"], - "enabled": client.get("enabled", True), - "system_prompts": client.get("system_prompts", {}), - "preset_group": client.get("preset_group", ""), - } - for dfl_key in client_cls.Meta().defaults.dict().keys(): - client_config[dfl_key] = client.get( - dfl_key, client.get("data", {}).get(dfl_key) - ) - - # find clients that have been removed - removed = existing - set(self.llm_clients.keys()) - if removed: - for agent_typ, agent_config in self.agents.items(): - if ( - "client" - in instance.agents.AGENT_CLASSES[agent_typ].config_options() - ): - agent = instance.get_agent(agent_typ) - if agent and agent.client and agent.client.name in removed: - agent_config["client"] = None - agent.client = None - instance.emit_agent_status(agent.__class__, agent) - - for name in removed: - log.debug("Destroying client", name=name) - await instance.destroy_client(name, self.config) - - self.config["clients"] = self.llm_clients - - await self.connect_llm_clients() - save_config(self.config) - - instance.sync_emit_clients_status() - - async def configure_agents(self, agents): - self.agents = {typ: {} for typ in instance.agent_types()} - - log.debug("Configuring agents") - - for agent in agents: - name = agent["name"] - - # special case for memory agent - if name == "memory" or name == "tts": - self.agents[name] = { - "name": name, - } - agent_instance = instance.get_agent(name, **self.agents[name]) - if agent_instance.has_toggle: - self.agents[name]["enabled"] = agent["enabled"] - - if getattr(agent_instance, "actions", None): - self.agents[name]["actions"] = agent.get("actions", {}) - - await agent_instance.apply_config(**self.agents[name]) - log.debug("Configured agent", name=name) - continue - - if name not in self.agents: - continue - - if isinstance(agent["client"], dict): - try: - client_name = agent["client"]["client"]["value"] - except KeyError: - continue - else: - client_name = agent["client"] - - if client_name not in self.llm_clients: - continue - - self.agents[name] = { - "client": self.llm_clients[client_name]["name"], - "name": name, - } - - agent_instance = instance.get_agent(name, **self.agents[name]) - - try: - agent_instance.client = self.llm_clients[client_name]["client"] - except KeyError: - self.llm_clients[client_name]["client"] = agent_instance.client = ( - instance.get_client(client_name) - ) - - if agent_instance.has_toggle: - self.agents[name]["enabled"] = agent["enabled"] - - if getattr(agent_instance, "actions", None): - self.agents[name]["actions"] = agent.get("actions", {}) - - await agent_instance.apply_config(**self.agents[name]) - - log.debug( - "Configured agent", - name=name, - client_name=self.llm_clients[client_name]["name"], - client=self.llm_clients[client_name]["client"], - ) - - self.config["agents"] = self.agents - save_config(self.config) - - instance.emit_agents_status() - def handle(self, emission: Emission): called = super().handle(emission) @@ -454,6 +234,8 @@ class WebsocketHandler(Receiver): "character": character, "action": emission.message_object.action, "direction_mode": direction_mode, + "subtype": emission.message_object.subtype, + "data": emission.data, "flags": ( int(emission.message_object.flags) if emission.message_object else 0 ), @@ -553,13 +335,15 @@ class WebsocketHandler(Receiver): } ) - def handle_config_saved(self, emission: Emission): - emission.data.update(system_prompt_defaults=SYSTEM_PROMPTS_CACHE) + async def on_config_changed(self, config: Config): + data = config.model_dump() + + data.update(system_prompt_defaults=SYSTEM_PROMPTS_CACHE) self.queue_put( { "type": "app_config", - "data": emission.data, + "data": data, } ) @@ -582,7 +366,12 @@ class WebsocketHandler(Receiver): ) def handle_client_status(self, emission: Emission): - client = instance.get_client(emission.id) + try: + client = instance.get_client(emission.id) + except KeyError: + return + + enable_api_auth = client.Meta().enable_api_auth if client else False self.queue_put( { "type": "client_status", @@ -593,7 +382,9 @@ class WebsocketHandler(Receiver): "data": emission.data, "max_token_length": client.max_token_length if client else 8192, "api_url": getattr(client, "api_url", None) if client else None, - "api_key": getattr(client, "api_key", None) if client else None, + "api_key": getattr(client, "api_key", None) + if enable_api_auth + else None, } ) diff --git a/src/talemate/server/world_state_manager/__init__.py b/src/talemate/server/world_state_manager/__init__.py index c0028450..72d99ddb 100644 --- a/src/talemate/server/world_state_manager/__init__.py +++ b/src/talemate/server/world_state_manager/__init__.py @@ -1,6 +1,7 @@ import asyncio import uuid from typing import Any, Union +import base64 import pydantic import structlog @@ -15,6 +16,7 @@ from talemate.server.websocket_plugin import Plugin from .scene_intent import SceneIntentMixin from .history import HistoryMixin +from .character import CharacterMixin log = structlog.get_logger("talemate.server.world_state_manager") @@ -184,7 +186,7 @@ class SuggestionPayload(pydantic.BaseModel): proposal_uid: str | None = None -class WorldStateManagerPlugin(SceneIntentMixin, HistoryMixin, Plugin): +class WorldStateManagerPlugin(SceneIntentMixin, HistoryMixin, CharacterMixin, Plugin): router = "world_state_manager" @property @@ -988,11 +990,23 @@ class WorldStateManagerPlugin(SceneIntentMixin, HistoryMixin, Plugin): payload = ExportOptions(**data) scene_data = await export(self.scene, payload) + # Handle different export formats + if isinstance(scene_data, bytes): + # ZIP export - encode as base64 for websocket transmission + exported_data = base64.b64encode(scene_data).decode() + file_extension = "zip" + else: + # Legacy JSON export - already base64 encoded + exported_data = scene_data + file_extension = "json" + self.websocket_handler.queue_put( { "type": "world_state_manager", "action": "scene_exported", - "data": scene_data, + "data": exported_data, + "format": payload.format.value, + "file_extension": file_extension, } ) await self.signal_operation_done() diff --git a/src/talemate/server/world_state_manager/character.py b/src/talemate/server/world_state_manager/character.py new file mode 100644 index 00000000..96485dea --- /dev/null +++ b/src/talemate/server/world_state_manager/character.py @@ -0,0 +1,62 @@ +import pydantic +import structlog + +log = structlog.get_logger("talemate.server.world_state_manager.character") + + +class UpdateCharacterVoicePayload(pydantic.BaseModel): + """Payload for updating a character voice.""" + + name: str + voice_id: str | None = None + + +class CharacterMixin: + """Mixin adding websocket handlers for character voice assignment.""" + + async def handle_update_character_voice(self, data: dict): + """Assign or clear a voice for a character. + + Expected payload + ----------------- + { + "type": "world_state_manager", + "action": "update_character_voice", + "name": "", + "voice_id": "" | null + } + """ + try: + payload = UpdateCharacterVoicePayload(**data) + except pydantic.ValidationError as e: + log.error("Invalid payload for update_character_voice", error=e) + await self.signal_operation_failed(str(e)) + return + + # Persist change via world state manager helper + try: + await self.world_state_manager.update_character_voice( + payload.name, payload.voice_id + ) + except Exception as e: + log.error( + "Failed to update character voice", character=payload.name, error=e + ) + await self.signal_operation_failed("Failed to update character voice") + return + + # Notify frontend + self.websocket_handler.queue_put( + { + "type": "world_state_manager", + "action": "character_voice_updated", + "data": payload.model_dump(), + } + ) + + # Re-emit updated character details so UI stays in sync + if hasattr(self, "handle_get_character_details"): + await self.handle_get_character_details({"name": payload.name}) + + await self.signal_operation_done() + self.scene.emit_status() diff --git a/src/talemate/tale_mate.py b/src/talemate/tale_mate.py index 6eede35b..6ce43d49 100644 --- a/src/talemate/tale_mate.py +++ b/src/talemate/tale_mate.py @@ -1,11 +1,10 @@ import asyncio import json import os -import random import re import traceback import uuid -from typing import Dict, Generator, List, Callable +from typing import Generator, Callable import isodate import structlog @@ -20,10 +19,9 @@ import talemate.save as save import talemate.util as util import talemate.world_state.templates as world_state_templates from talemate.agents.context import active_agent -from talemate.config import load_config +from talemate.config import Config, get_config from talemate.context import interaction -from talemate.emit import Emitter, emit, wait_for_input -from talemate.emit.signals import handlers +from talemate.emit import Emitter, emit from talemate.exceptions import ( ExitScene, LLMAccuracyError, @@ -34,7 +32,6 @@ from talemate.exceptions import ( GenerationCancelled, ) from talemate.game.state import GameState -from talemate.instance import get_agent from talemate.scene_assets import SceneAssets from talemate.scene_message import ( CharacterMessage, @@ -54,569 +51,29 @@ from talemate.game.engine.nodes.layout import load_graph from talemate.game.engine.nodes.packaging import initialize_packages from talemate.scene.intent import SceneIntent from talemate.history import emit_archive_add, ArchiveEntry +from talemate.character import Character +from talemate.agents.tts.schema import VoiceLibrary +from talemate.instance import get_agent __all__ = [ "Character", "Actor", "Scene", - "Helper", "Player", ] log = structlog.get_logger("talemate") -async_signals.register("scene_init") -async_signals.register("game_loop_start") -async_signals.register("game_loop") -async_signals.register("game_loop_actor_iter") -async_signals.register("game_loop_new_message") -async_signals.register("player_turn_start") - - -class Character: - """ - A character for the AI to roleplay, with a name, description, and greeting text. - """ - - def __init__( - self, - name: str, - description: str = "", - greeting_text: str = "", - gender: str = "female", - color: str = "cyan", - example_dialogue: List[str] = [], - is_player: bool = False, - history_events: list[dict] = None, - base_attributes: dict = None, - details: dict[str, str] = None, - **kwargs, - ): - self.name = name - self.description = description - self.greeting_text = greeting_text - self.example_dialogue = example_dialogue - self.gender = gender - self.color = color - self.is_player = is_player - self.history_events = history_events or [] - self.base_attributes = base_attributes or {} - self.details = details or {} - self.cover_image = kwargs.get("cover_image") - self.dialogue_instructions = kwargs.get("dialogue_instructions") - - self.memory_dirty = False - - @property - def persona(self): - return self.description - - @property - def serialize(self) -> Dict[str, str]: - return { - "name": self.name, - "description": self.description, - "greeting_text": self.greeting_text, - "base_attributes": self.base_attributes, - "details": self.details, - "gender": self.gender, - "color": self.color, - "example_dialogue": self.example_dialogue, - "history_events": self.history_events, - "is_player": self.is_player, - "cover_image": self.cover_image, - "dialogue_instructions": self.dialogue_instructions, - } - - @property - def sheet(self) -> str: - sheet = self.base_attributes or { - "name": self.name, - "gender": self.gender, - "description": self.description, - } - - sheet_list = [] - - for key, value in sheet.items(): - sheet_list.append(f"{key}: {value}") - - return "\n".join(sheet_list) - - @property - def random_dialogue_example(self): - """ - Get a random example dialogue line for this character. - - Returns: - str: The random example dialogue line. - """ - if not self.example_dialogue: - return "" - - return random.choice(self.example_dialogue) - - def __repr__(self): - return f"Character: {self.name}" - - def set_color(self, color: str = None): - # if no color provided, chose a random color - - if color is None: - color = util.random_color() - self.color = color - - def set_cover_image(self, asset_id: str, initial_only: bool = False): - if self.cover_image and initial_only: - return - - self.cover_image = asset_id - - def sheet_filtered(self, *exclude): - sheet = self.base_attributes or { - "name": self.name, - "gender": self.gender, - "description": self.description, - } - - sheet_list = [] - - for key, value in sheet.items(): - if key not in exclude: - sheet_list.append(f"{key}: {value}") - - return "\n".join(sheet_list) - - def random_dialogue_examples( - self, - scene: "Scene", - num: int = 3, - strip_name: bool = False, - max_backlog: int = 250, - max_length: int = 192, - ) -> list[str]: - """ - Get multiple random example dialogue lines for this character. - - Will return up to `num` examples and not have any duplicates. - """ - - history_examples = self._random_dialogue_examples_from_history( - scene, num, max_backlog - ) - - if len(history_examples) < num: - random_examples = self._random_dialogue_examples( - num - len(history_examples), strip_name - ) - - for example in random_examples: - history_examples.append(example) - - # ensure sane example lengths - - history_examples = [ - util.strip_partial_sentences(example[:max_length]) - for example in history_examples - ] - - log.debug("random_dialogue_examples", history_examples=history_examples) - return history_examples - - def _random_dialogue_examples_from_history( - self, scene: "Scene", num: int = 3, max_backlog: int = 250 - ) -> list[str]: - """ - Get multiple random example dialogue lines for this character from the scene's history. - - Will checks the last `max_backlog` messages in the scene's history and returns up to `num` examples. - """ - - history = scene.history[-max_backlog:] - - examples = [] - - for message in history: - if not isinstance(message, CharacterMessage): - continue - - if message.character_name != self.name: - continue - - examples.append(message.without_name.strip()) - - if not examples: - return [] - - return random.sample(examples, min(num, len(examples))) - - def _random_dialogue_examples( - self, num: int = 3, strip_name: bool = False - ) -> list[str]: - """ - Get multiple random example dialogue lines for this character. - - Will return up to `num` examples and not have any duplicates. - """ - - if not self.example_dialogue: - return [] - - # create copy of example_dialogue so we dont modify the original - - examples = self.example_dialogue.copy() - - # shuffle the examples so we get a random order - - random.shuffle(examples) - - # now pop examples until we have `num` examples or we run out of examples - - if strip_name: - examples = [example.split(":", 1)[1].strip() for example in examples] - - return [examples.pop() for _ in range(min(num, len(examples)))] - - def filtered_sheet(self, attributes: list[str]): - """ - Same as sheet but only returns the attributes in the given list - - Attributes that dont exist will be ignored - """ - - sheet_list = [] - - for key, value in self.base_attributes.items(): - if key.lower() not in attributes: - continue - sheet_list.append(f"{key}: {value}") - - return "\n".join(sheet_list) - - def rename(self, new_name: str): - """ - Rename the character. - - Args: - new_name (str): The new name of the character. - - Returns: - None - """ - - orig_name = self.name - self.name = new_name - - if orig_name.lower() == "you": - # we dont want to replace "you" in the description - # or anywhere else so we can just return here - return - - if self.description: - self.description = self.description.replace(f"{orig_name}", self.name) - for k, v in self.base_attributes.items(): - if isinstance(v, str): - self.base_attributes[k] = v.replace(f"{orig_name}", self.name) - for i, v in list(self.details.items()): - if isinstance(v, str): - self.details[i] = v.replace(f"{orig_name}", self.name) - self.memory_dirty = True - - def introduce_main_character(self, character): - """ - Makes this character aware of the main character's name in the scene. - - This will replace all occurrences of {{user}} (case-insensitive) in all of the character's properties - with the main character's name. - """ - - properties = ["description", "greeting_text"] - - pattern = re.compile(re.escape("{{user}}"), re.IGNORECASE) - - for prop in properties: - prop_value = getattr(self, prop) - - try: - updated_prop_value = pattern.sub(character.name, prop_value) - except Exception as e: - log.error( - "introduce_main_character", - error=e, - traceback=traceback.format_exc(), - ) - updated_prop_value = prop_value - setattr(self, prop, updated_prop_value) - - # also replace in all example dialogue - - for i, dialogue in enumerate(self.example_dialogue): - self.example_dialogue[i] = pattern.sub(character.name, dialogue) - - def update(self, **kwargs): - """ - Update character properties with given key-value pairs. - """ - - for key, value in kwargs.items(): - setattr(self, key, value) - - self.memory_dirty = True - - async def purge_from_memory(self): - """ - Purges this character's details from memory. - """ - memory_agent = get_agent("memory") - await memory_agent.delete({"character": self.name}) - log.info("purged character from memory", character=self.name) - - async def commit_to_memory(self, memory_agent): - """ - Commits this character's details to the memory agent. (vectordb) - """ - - items = [] - - if not self.base_attributes or "description" not in self.base_attributes: - if not self.description: - self.description = "" - description_chunks = [ - chunk.strip() for chunk in self.description.split("\n") if chunk.strip() - ] - - for idx in range(len(description_chunks)): - chunk = description_chunks[idx] - - items.append( - { - "text": f"{self.name}: {chunk}", - "id": f"{self.name}.description.{idx}", - "meta": { - "character": self.name, - "attr": "description", - "typ": "base_attribute", - }, - } - ) - - seen_attributes = set() - - for attr, value in self.base_attributes.items(): - if attr.startswith("_"): - continue - - if attr.lower() in ["name", "scenario_context", "_prompt", "_template"]: - continue - - seen_attributes.add(attr) - - items.append( - { - "text": f"{self.name}'s {attr}: {value}", - "id": f"{self.name}.{attr}", - "meta": { - "character": self.name, - "attr": attr, - "typ": "base_attribute", - }, - } - ) - - for key, detail in self.details.items(): - # if colliding with attribute name, prefix with detail_ - if key in seen_attributes: - key = f"detail_{key}" - - items.append( - { - "text": f"{self.name} - {key}: {detail}", - "id": f"{self.name}.{key}", - "meta": { - "character": self.name, - "typ": "details", - "detail": key, - }, - } - ) - - # await memory_agent.add(detail, None) - - for history_event in self.history_events: - if not history_event or not history_event["summary"]: - continue - - items.append( - { - "text": history_event["summary"], - "meta": { - "character": self.name, - "typ": "history_event", - }, - } - ) - - # await memory_agent.add(history_event["summary"], None) - - if items: - await memory_agent.add_many(items) - - self.memory_dirty = False - - async def commit_single_attribute_to_memory( - self, memory_agent, attribute: str, value: str - ): - """ - Commits a single attribute to memory - """ - - items = [] - - # remove old attribute if it exists - - await memory_agent.delete( - {"character": self.name, "typ": "base_attribute", "attr": attribute} - ) - - self.base_attributes[attribute] = value - - items.append( - { - "text": f"{self.name}'s {attribute}: {self.base_attributes[attribute]}", - "id": f"{self.name}.{attribute}", - "meta": { - "character": self.name, - "attr": attribute, - "typ": "base_attribute", - }, - } - ) - - log.debug("commit_single_attribute_to_memory", items=items) - - await memory_agent.add_many(items) - - async def commit_single_detail_to_memory( - self, memory_agent, detail: str, value: str - ): - """ - Commits a single detail to memory - """ - - items = [] - - # remove old detail if it exists - - await memory_agent.delete( - {"character": self.name, "typ": "details", "detail": detail} - ) - - self.details[detail] = value - - items.append( - { - "text": f"{self.name} - {detail}: {value}", - "id": f"{self.name}.{detail}", - "meta": { - "character": self.name, - "typ": "details", - "detail": detail, - }, - } - ) - - log.debug("commit_single_detail_to_memory", items=items) - - await memory_agent.add_many(items) - - async def set_detail(self, name: str, value): - memory_agent = get_agent("memory") - if not value: - try: - del self.details[name] - await memory_agent.delete( - {"character": self.name, "typ": "details", "detail": name} - ) - except KeyError: - pass - else: - self.details[name] = value - await self.commit_single_detail_to_memory(memory_agent, name, value) - - def set_detail_defer(self, name: str, value): - self.details[name] = value - self.memory_dirty = True - - def get_detail(self, name: str): - return self.details.get(name) - - async def set_base_attribute(self, name: str, value): - memory_agent = get_agent("memory") - - if not value: - try: - del self.base_attributes[name] - await memory_agent.delete( - {"character": self.name, "typ": "base_attribute", "attr": name} - ) - except KeyError: - pass - else: - self.base_attributes[name] = value - await self.commit_single_attribute_to_memory(memory_agent, name, value) - - def set_base_attribute_defer(self, name: str, value): - self.base_attributes[name] = value - self.memory_dirty = True - - def get_base_attribute(self, name: str): - return self.base_attributes.get(name) - - async def set_description(self, description: str): - memory_agent = get_agent("memory") - self.description = description - - items = [] - - await memory_agent.delete( - {"character": self.name, "typ": "base_attribute", "attr": "description"} - ) - - description_chunks = [ - chunk.strip() for chunk in self.description.split("\n") if chunk.strip() - ] - - for idx in range(len(description_chunks)): - chunk = description_chunks[idx] - - items.append( - { - "text": f"{self.name}: {chunk}", - "id": f"{self.name}.description.{idx}", - "meta": { - "character": self.name, - "attr": "description", - "typ": "base_attribute", - }, - } - ) - - await memory_agent.add_many(items) - - -class Helper: - """ - Wrapper for non-conversational agents, such as summarization agents - """ - - def __init__(self, agent: agents.Agent, **options): - self.agent = agent - self.options = options - - @property - def agent_type(self): - return self.agent.agent_type +async_signals.register( + "scene_init", + "scene_init_after", + "game_loop_start", + "game_loop", + "game_loop_actor_iter", + "game_loop_new_message", + "player_turn_start", +) class Actor: @@ -670,6 +127,7 @@ class Scene(Emitter): self.inactive_characters = {} self.layered_history = [] self.assets = SceneAssets(scene=self) + self.voice_library: VoiceLibrary = VoiceLibrary() self.description = "" self.intro = "" self.outline = "" @@ -695,8 +153,6 @@ class Scene(Emitter): # happen as save-as and not overwrite the original self.immutable_save = False - self.config = load_config() - self.context = "" self.commands = commands.Manager(self) self.environment = "scene" @@ -735,12 +191,17 @@ class Scene(Emitter): "game_loop_new_message": async_signals.get("game_loop_new_message"), "scene_init": async_signals.get("scene_init"), "player_turn_start": async_signals.get("player_turn_start"), + "config.changed": async_signals.get("config.changed"), } self.setup_emitter(scene=self) self.world_state.emit() + @property + def config(self) -> Config: + return get_config() + @property def main_character(self) -> Actor | None: try: @@ -761,6 +222,22 @@ class Scene(Emitter): for actor in self.actors: yield actor.character + @property + def all_characters(self) -> Generator[Character, None, None]: + """ + Returns all characters in the scene, including inactive characters + """ + + for actor in self.actors: + yield actor.character + + for character in self.inactive_characters.values(): + yield character + + @property + def all_character_names(self): + return [character.name for character in self.all_characters] + @property def npcs(self): for actor in self.actors: @@ -853,11 +330,11 @@ class Scene(Emitter): @property def auto_save(self): - return self.config.get("game", {}).get("general", {}).get("auto_save", True) + return self.config.game.general.auto_save @property def auto_progress(self): - return self.config.get("game", {}).get("general", {}).get("auto_progress", True) + return self.config.game.general.auto_progress @property def world_state_manager(self) -> WorldStateManager: @@ -865,7 +342,7 @@ class Scene(Emitter): @property def conversation_format(self): - return self.get_helper("conversation").agent.conversation_format + return get_agent("conversation").conversation_format @property def writing_style(self) -> world_state_templates.WritingStyle | None: @@ -880,7 +357,7 @@ class Scene(Emitter): @property def max_backscroll(self): - return self.config.get("game", {}).get("general", {}).get("max_backscroll", 512) + return self.config.game.general.max_backscroll @property def nodes_filename(self): @@ -934,19 +411,18 @@ class Scene(Emitter): """ connect scenes to signals """ - handlers["config_saved"].connect(self.on_config_saved) + self.signals["config.changed"].connect(self.on_config_changed) def disconnect(self): """ disconnect scenes from signals """ - handlers["config_saved"].disconnect(self.on_config_saved) + self.signals["config.changed"].disconnect(self.on_config_changed) def __del__(self): self.disconnect() - def on_config_saved(self, event): - self.config = event.data + async def on_config_changed(self, event): self.emit_status() def recent_history(self, max_tokens: int = 2048): @@ -1341,9 +817,8 @@ class Scene(Emitter): if actor.character.base_attributes.get("scenario overview"): self.description = actor.character.base_attributes["scenario overview"] - memory_helper = self.get_helper("memory") - if memory_helper: - await actor.character.commit_to_memory(memory_helper.agent) + memory = get_agent("memory") + await actor.character.commit_to_memory(memory) async def remove_character( self, character: Character, purge_from_memory: bool = True @@ -1376,22 +851,6 @@ class Scene(Emitter): actor.character = None - def add_helper(self, helper: Helper): - """ - Add a helper to the scene - """ - self.helpers.append(helper) - helper.agent.connect(self) - - def get_helper(self, agent_type): - """ - Returns the helper of the given agent class if it exists - """ - - for helper in self.helpers: - if helper.agent_type == agent_type: - return helper - def get_character(self, character_name: str, partial: bool = False): """ Returns the character with the given name if it exists @@ -1496,7 +955,7 @@ class Scene(Emitter): except AttributeError: intro = self.intro - editor = self.get_helper("editor").agent + editor = get_agent("editor") if editor.fix_exposition_enabled and editor.fix_exposition_narrator: if '"' not in intro and "*" not in intro: @@ -1544,10 +1003,8 @@ class Scene(Emitter): show_hidden = kwargs.get("show_hidden", False) conversation_format = self.conversation_format - actor_direction_mode = self.get_helper("director").agent.actor_direction_mode - layered_history_enabled = self.get_helper( - "summarizer" - ).agent.layered_history_enabled + actor_direction_mode = get_agent("director").actor_direction_mode + layered_history_enabled = get_agent("summarizer").layered_history_enabled include_reinforcements = kwargs.get("include_reinforcements", True) assured_dialogue_num = kwargs.get("assured_dialogue_num", 5) @@ -1808,7 +1265,7 @@ class Scene(Emitter): "inactive_characters": list(self.inactive_characters.keys()), "context": self.context, "assets": self.assets.dict(), - "characters": [actor.character.serialize for actor in self.actors], + "characters": [actor.character.model_dump() for actor in self.actors], "character_colors": { character.name: character.color for character in self.get_characters() @@ -2014,7 +1471,7 @@ class Scene(Emitter): self.active_pins = list(_active_pins.pins.values()) async def ensure_memory_db(self): - memory = self.get_helper("memory").agent + memory = get_agent("memory") if not memory.db: await memory.set_db() @@ -2196,11 +1653,10 @@ class Scene(Emitter): self.filename = copy_name if not self.name and not auto: - self.name = await wait_for_input("Enter scenario name: ") - self.filename = "base.json" + raise TalemateError("Scene has no name, cannot save") elif not self.filename and not auto: - self.filename = await wait_for_input("Enter save name: ") + self.filename = str(uuid.uuid4())[:10] self.filename = self.filename.replace(" ", "-").lower() + ".json" if self.filename and not self.filename.endswith(".json"): @@ -2212,7 +1668,7 @@ class Scene(Emitter): if save_as: self.immutable_save = False - memory_agent = self.get_helper("memory").agent + memory_agent = get_agent("memory") memory_agent.close_db(self) self.memory_id = str(uuid.uuid4())[:10] await self.commit_to_memory() @@ -2264,14 +1720,14 @@ class Scene(Emitter): async def add_to_recent_scenes(self): log.debug("add_to_recent_scenes", filename=self.filename) - config = load_config(as_model=True) + config = get_config() config.recent_scenes.push(self) - config.save() + await config.set_dirty() async def commit_to_memory(self): # will recommit scene to long term memory - memory = self.get_helper("memory").agent + memory = get_agent("memory") memory.drop_db() await memory.set_db() @@ -2308,7 +1764,7 @@ class Scene(Emitter): self.actors = [] async def reset_memory(self): - memory_agent = self.get_helper("memory").agent + memory_agent = get_agent("memory") memory_agent.close_db(self) self.memory_id = str(uuid.uuid4())[:10] await self.commit_to_memory() @@ -2334,7 +1790,7 @@ class Scene(Emitter): await load_scene( self, os.path.join(self.save_dir, self.restore_from), - self.get_helper("conversation").agent.client, + get_agent("conversation").client, ) await self.reset_memory() @@ -2371,9 +1827,9 @@ class Scene(Emitter): "environment": scene.environment, "archived_history": scene.archived_history, "layered_history": scene.layered_history, - "characters": [actor.character.serialize for actor in scene.actors], + "characters": [actor.character.model_dump() for actor in scene.actors], "inactive_characters": { - name: character.serialize + name: character.model_dump() for name, character in scene.inactive_characters.items() }, "context": scene.context, @@ -2406,3 +1862,6 @@ class Scene(Emitter): if self.cancel_requested: self.cancel_requested = False raise GenerationCancelled("action cancelled") + + +Character.model_rebuild() diff --git a/src/talemate/util/dialogue.py b/src/talemate/util/dialogue.py index bed08833..8219160e 100644 --- a/src/talemate/util/dialogue.py +++ b/src/talemate/util/dialogue.py @@ -1,5 +1,8 @@ import re import structlog +import pydantic +from typing import Literal +from nltk.tokenize import sent_tokenize __all__ = [ "handle_endofline_special_delimiter", @@ -14,11 +17,21 @@ __all__ = [ "ensure_dialog_line_format", "clean_uneven_markers", "split_anchor_text", + "separate_dialogue_from_exposition", + "parse_tts_markup", + "separate_sentences", + "DialogueChunk", ] log = structlog.get_logger("talemate.util.dialogue") +class DialogueChunk(pydantic.BaseModel): + text: str + type: Literal["dialogue", "exposition"] + speaker: str | None = None + + def handle_endofline_special_delimiter(content: str) -> str: # END-OF-LINE is a custom delimter that can exist 0 to n times # it should split total_result on the last one, take the left side @@ -428,3 +441,132 @@ def split_anchor_text(text: str, anchor_length: int = 10) -> tuple[str, str]: anchor = " ".join(words[mid_point:]) return non_anchor, anchor + + +def separate_sentences(text: str) -> str: + """ + Separates a text into sentences and joins them with double newlines. + """ + return "\n\n".join(sent_tokenize(text)) + + +def parse_tts_markup(markup: str) -> list[DialogueChunk]: + """ + Parses TTS markup in the format: + [Speaker Name] text content + [Narrator] narrative text + + Returns a list of DialogueChunk objects. + """ + if not markup: + return [] + + chunks = [] + lines = markup.strip().split("\n") + + for line in lines: + line = line.strip() + if not line: + continue + + # Match pattern [Speaker Name] followed by text + match = re.match(r"^\[([^\]]+)\]\s*(.*)", line) + if match: + speaker_name = match.group(1).strip() + text_content = match.group(2).strip() + + if not text_content: + continue + + # Determine if this is dialogue or exposition + if speaker_name.lower() == "narrator": + chunk_type = "exposition" + speaker = None + else: + chunk_type = "dialogue" + speaker = speaker_name + + chunks.append( + DialogueChunk(text=text_content, type=chunk_type, speaker=speaker) + ) + else: + # Line doesn't match expected format, treat as exposition + if line: + chunks.append(DialogueChunk(text=line, type="exposition", speaker=None)) + + return chunks + + +def separate_dialogue_from_exposition(text: str) -> list[DialogueChunk]: + """ + Separates dialogue from exposition in a text. + + Returns a list of DialogueChunk objects, where each chunk is either dialogue or exposition. + Dialogue is defined as any text between double quotes (""). + Everything else is exposition (regardless of asterisks or other markers). + + Speakers may be identified by curly braces, e.g. {John} - if so they are always at the beginning of a dialogue segment. + + For example: + + "{John}I am leaving now." - speaker is John + "I am leaving now." - speaker is narrator (or not identified`) + """ + if not text: + return [] + + chunks = [] + current_segment = "" + in_dialogue = False + prev_speaker: str | None = None + + for i, char in enumerate(text): + if char == '"': + # Quote marks are transition points + if in_dialogue: + # We're ending a dialogue segment - include the closing quote + current_segment += char + + # Detect speaker names of the form "{Name}" that immediately follow the opening quote. + # Example: "{John}Hello." -> speaker="John", text='"Hello."' + + speaker = None + speaker_match = re.match(r'^"\{([^}]+)\}', current_segment) + if speaker_match: + # speaker_match.group(1) contains the name without braces + speaker = speaker_match.group(1) + # Remove the curly-braced name but keep the leading quote + current_segment = current_segment.replace(f"{{{speaker}}}", "", 1) + + prev_speaker = speaker + elif prev_speaker: + speaker = prev_speaker + + chunks.append( + DialogueChunk( + text=current_segment, + type="dialogue", + speaker=speaker, + ) + ) + current_segment = "" + in_dialogue = False + else: + # We're starting a dialogue segment + if current_segment: + # Save any exposition before the dialogue + chunks.append( + DialogueChunk(text=current_segment, type="exposition") + ) + current_segment = char + in_dialogue = True + else: + # Regular character - add to current segment + current_segment += char + + # Don't forget the last segment if it exists + if current_segment: + chunk_type = "dialogue" if in_dialogue else "exposition" + chunks.append(DialogueChunk(text=current_segment, type=chunk_type)) + + return chunks diff --git a/src/talemate/ux/schema.py b/src/talemate/ux/schema.py index c4cba71a..9835e4ef 100644 --- a/src/talemate/ux/schema.py +++ b/src/talemate/ux/schema.py @@ -2,11 +2,42 @@ import pydantic __all__ = [ "Note", + "Field", + "Column", ] +class Action(pydantic.BaseModel): + action_name: str + arguments: list[str | int | float | bool] = pydantic.Field(default_factory=list) + label: str = None + icon: str = None + + class Note(pydantic.BaseModel): text: str title: str = None color: str = "muted" icon: str = "mdi-information-outline" + + actions: list[Action] = pydantic.Field(default_factory=list) + + +class Field(pydantic.BaseModel): + name: str + label: str + type: str + value: int | float | str | bool | list | None = None + choices: list[dict[str, str | int | float | bool]] = pydantic.Field( + default_factory=list + ) + max: int | float | None = None + min: int | float | None = None + step: int | float | None = None + description: str = "" + + required: bool = False + + +class Column(Field): + pass diff --git a/src/talemate/version.py b/src/talemate/version.py index b2053a32..5b77e36e 100644 --- a/src/talemate/version.py +++ b/src/talemate/version.py @@ -1,3 +1,3 @@ __all__ = ["VERSION"] -VERSION = "0.31.0" +VERSION = "0.32.0" diff --git a/src/talemate/world_state/manager.py b/src/talemate/world_state/manager.py index b5f753a6..7f680735 100644 --- a/src/talemate/world_state/manager.py +++ b/src/talemate/world_state/manager.py @@ -4,7 +4,8 @@ import pydantic import structlog import talemate.world_state.templates as world_state_templates -from talemate.character import activate_character, deactivate_character +from talemate.agents.tts.util import get_voice +from talemate.character import activate_character, deactivate_character, set_voice from talemate.instance import get_agent from talemate.emit import emit from talemate.world_state import ( @@ -13,6 +14,7 @@ from talemate.world_state import ( Reinforcement, Suggestion, ) +from talemate.agents.tts.schema import Voice if TYPE_CHECKING: from talemate.tale_mate import Character, Scene @@ -52,6 +54,7 @@ class CharacterDetails(pydantic.BaseModel): actor: CharacterActor = pydantic.Field(default_factory=CharacterActor) cover_image: Union[str, None] = None color: Union[str, None] = None + voice: Union[Voice, None] = None class World(pydantic.BaseModel): @@ -163,6 +166,7 @@ class WorldStateManager: ), cover_image=character.cover_image, color=character.color, + voice=character.voice, ) # sorted base attributes @@ -317,6 +321,29 @@ class WorldStateManager: character.set_color(color) + async def update_character_voice(self, character_name: str, voice_id: str | None): + """Assign or clear a voice for the given character. + + Args: + character_name: Name of the character to update. + voice_id: The unique id of the voice in the voice library ("provider:voice_id"). + If *None* or empty string, the character voice assignment is cleared. + """ + character = self.scene.get_character(character_name) + if not character: + log.error("character not found", character_name=character_name) + return + + if voice_id: + voice = get_voice(self.scene, voice_id) + if not voice: + log.warning("voice not found in library", voice_id=voice_id) + + await set_voice(character, voice) + else: + # Clear voice assignment + await set_voice(character, None) + async def update_character_actor( self, character_name: str, diff --git a/start-frontend.sh b/start-frontend.sh index aa5ebc26..71d9a4db 100755 --- a/start-frontend.sh +++ b/start-frontend.sh @@ -1,2 +1,2 @@ cd talemate_frontend -npm run serve \ No newline at end of file +npm run serve -- --host 0.0.0.0 --port 8080 \ No newline at end of file diff --git a/talemate_frontend/README.md b/talemate_frontend/README.md index 18a618c5..0467f1cd 100644 --- a/talemate_frontend/README.md +++ b/talemate_frontend/README.md @@ -21,4 +21,4 @@ npm run lint ``` ### Customize configuration -See [Configuration Reference](https://cli.vuejs.org/config/). +See [Configuration Reference](https://vite.dev/config/). diff --git a/talemate_frontend/babel.config.js b/talemate_frontend/babel.config.js deleted file mode 100644 index e9558405..00000000 --- a/talemate_frontend/babel.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - presets: [ - '@vue/cli-plugin-babel/preset' - ] -} diff --git a/talemate_frontend/eslint.config.js b/talemate_frontend/eslint.config.js new file mode 100644 index 00000000..11de21eb --- /dev/null +++ b/talemate_frontend/eslint.config.js @@ -0,0 +1,23 @@ +import js from "@eslint/js"; +import pluginVue from "eslint-plugin-vue"; +import globals from "globals"; + +export default [ + { + ignores: ["dist/**/*", "node_modules/**/*", "*.min.js"], + }, + js.configs.recommended, + ...pluginVue.configs["flat/essential"], + { + languageOptions: { + ecmaVersion: "latest", + sourceType: "module", + globals: { + node: true, + ...globals.browser, + ...globals.node, + }, + }, + rules: {}, + }, +]; diff --git a/talemate_frontend/example.env.development.local b/talemate_frontend/example.env.development.local index 5cae18be..5a1597e4 100644 --- a/talemate_frontend/example.env.development.local +++ b/talemate_frontend/example.env.development.local @@ -1,3 +1,3 @@ ALLOWED_HOSTS=example.com # wss if behind ssl, ws if not -VUE_APP_TALEMATE_BACKEND_WEBSOCKET_URL=wss://example.com:5050 \ No newline at end of file +VITE_TALEMATE_BACKEND_WEBSOCKET_URL=wss://example.com:5050 \ No newline at end of file diff --git a/talemate_frontend/public/index.html b/talemate_frontend/index.html similarity index 55% rename from talemate_frontend/public/index.html rename to talemate_frontend/index.html index 03536544..1de8925d 100644 --- a/talemate_frontend/public/index.html +++ b/talemate_frontend/index.html @@ -4,14 +4,14 @@ - + Talemate
- + diff --git a/talemate_frontend/package-lock.json b/talemate_frontend/package-lock.json index 36af26a0..067e29c0 100644 --- a/talemate_frontend/package-lock.json +++ b/talemate_frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "talemate_frontend", - "version": "0.31.0", + "version": "0.32.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "talemate_frontend", - "version": "0.31.0", + "version": "0.32.0", "dependencies": { "@codemirror/lang-json": "^6.0.1", "@codemirror/lang-markdown": "^6.2.5", @@ -19,330 +19,22 @@ "core-js": "^3.37.1", "dot-prop": "^9.0.0", "litegraph.js": "^0.7.18", + "lodash": "^4.17.21", "marked": "^15.0.12", "roboto-fontface": "*", "uuid": "^10.0.0", "vue": "^3.5", "vue-codemirror": "^6.1.1", - "vuetify": "^3.8", + "vuetify": "^3.9", "webfontloader": "^1.6.28" }, "devDependencies": { - "@babel/core": "^7", - "@babel/eslint-parser": "^7", - "@vue/cli-plugin-babel": "~5.0.8", + "@vitejs/plugin-vue": "^6.0.0", "eslint": "^9.0.0", "eslint-plugin-vue": "^10.0.0", "postcss": "^8.5.3", - "vue-cli-plugin-vuetify": "~2.5.8", - "webpack-plugin-vuetify": "^3.1" - } - }, - "node_modules/@achrinza/node-ipc": { - "version": "9.2.9", - "resolved": "https://registry.npmjs.org/@achrinza/node-ipc/-/node-ipc-9.2.9.tgz", - "integrity": "sha512-7s0VcTwiK/0tNOVdSX9FWMeFdOEcsAOz9HesBldXxFMaGvIak7KC2z9tV9EgsQXn6KUsWsfIkViMNuIo0GoZDQ==", - "dev": true, - "dependencies": { - "@node-ipc/js-queue": "2.0.3", - "event-pubsub": "4.3.0", - "js-message": "1.0.7" - }, - "engines": { - "node": "8 || 9 || 10 || 11 || 12 || 13 || 14 || 15 || 16 || 17 || 18 || 19 || 20 || 21 || 22" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", - "dev": true, - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/eslint-parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.27.5.tgz", - "integrity": "sha512-HLkYQfRICudzcOtjGwkPvGc5nF1b4ljLZh1IRDj50lRZ718NAKVgQpIAUX8bfg6u/yuSKY3L7E0YzIV+OxrB8Q==", - "dev": true, - "dependencies": { - "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", - "eslint-visitor-keys": "^2.1.0", - "semver": "^6.3.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || >=14.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.11.0", - "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" + "vite": "^7.0.4", + "vite-plugin-vuetify": "^2.1.1" } }, "node_modules/@babel/helper-string-parser": { @@ -361,48 +53,12 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", - "dev": true, - "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", - "dev": true, - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -411,1168 +67,10 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz", - "integrity": "sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-decorators": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", - "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", - "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz", - "integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz", - "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz", - "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==", - "dev": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.3", - "@babel/plugin-transform-parameters": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz", - "integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz", - "integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", - "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.27.1", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.27.1", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.27.2", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.1", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.27.1", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", - "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", + "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" @@ -1617,9 +115,9 @@ } }, "node_modules/@codemirror/lang-cpp": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.2.tgz", - "integrity": "sha512-6oYEYUKHvrnacXxWxYa6t4puTlbN3dgV662BDfSH8+MfjQjVmP697/KYTDOqpxgerkvoNm7q5wlFMBeX8ZMocg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/cpp": "^1.0.0" @@ -1666,9 +164,9 @@ } }, "node_modules/@codemirror/lang-java": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.1.tgz", - "integrity": "sha512-OOnmhH67h97jHzCuFaIEspbmsT98fNdhVhmA3zCxW0cn7l8rChDhZtwiwJ/JOKXgfm4J+ELxQihxaI7bj7mJRg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/java": "^1.0.0" @@ -1689,9 +187,9 @@ } }, "node_modules/@codemirror/lang-json": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", - "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" @@ -1725,9 +223,9 @@ } }, "node_modules/@codemirror/lang-markdown": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.2.tgz", - "integrity": "sha512-c/5MYinGbFxYl4itE9q/rgN/sMTjOr8XL5OWnC+EaRMLfCbVUmmubTJfdgpfcSS2SCaT7b+Q+xi3l6CgoE+BsA==", + "version": "6.3.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.3.3.tgz", + "integrity": "sha512-1fn1hQAPWlSSMCvnF810AkhWpNLkJpl66CRfIy3vVl20Sl4NwChkorCHqpMtNbXr1EuMJsrDnhEpjZxKZ2UX3A==", "dependencies": { "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", @@ -1739,9 +237,9 @@ } }, "node_modules/@codemirror/lang-php": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.1.tgz", - "integrity": "sha512-ublojMdw/PNWa7qdN5TMsjmqkNuTBD3k6ndZ4Z0S25SBAiweFGyY68AS3xNcIOlb6DDFDvKlinLQ40vSLqf8xA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", @@ -1763,9 +261,9 @@ } }, "node_modules/@codemirror/lang-rust": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.1.tgz", - "integrity": "sha512-344EMWFBzWArHWdZn/NcgkwMvZIWUR1GEBdwG8FEp++6o6vT6KL9V7vGs2ONsKxxFUPXKI0SPcWhyYyl2zPYxQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/rust": "^1.0.0" @@ -1784,9 +282,9 @@ } }, "node_modules/@codemirror/lang-sql": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.9.0.tgz", - "integrity": "sha512-xmtpWqKSgum1B1J3Ro6rf7nuPqf2+kJQg5SjrofCAcyCThOe0ihSktSoXfXuhQBnwx1QbmreBbLJM5Jru6zitg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.9.1.tgz", + "integrity": "sha512-ecSk3gm/mlINcURMcvkCZmXgdzPSq8r/yfCtTB4vgqGGIbBC2IJIAy7GqYTy5pgBEooTVmHP2GZK6Z7h63CDGg==", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", @@ -1848,9 +346,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.1.tgz", - "integrity": "sha512-5kS1U7emOGV84vxC+ruBty5sUgcD0te6dyupyRVG2zaSjhTDM73LhVKUtVwiqSe6QwmEoA4SCiU8AKPFyumAWQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.2.tgz", + "integrity": "sha512-p44TsNArL4IVXDTbapUmEkAlvWs2CFQbcfc0ymDsis1kH2wh0gcY96AS29c/vp2d0y2Tquk1EDSaawpzilUiAw==", "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", @@ -1927,9 +425,9 @@ } }, "node_modules/@codemirror/theme-one-dark": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.2.tgz", - "integrity": "sha512-F+sH0X16j/qFLMAfbciKTxVOwkdAS336b7AXTKOZhy8BR3eH/RelsnLgLFINrpST63mmN2OuwUt0W2ndUgYwUA==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", @@ -1938,9 +436,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.37.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.37.1.tgz", - "integrity": "sha512-Qy4CAUwngy/VQkEz0XzMKVRcckQuqLYWKqVpDDDghBe5FSXSqfVrJn49nw3ePZHxRUz4nRmb05Lgi+9csWo4eg==", + "version": "6.38.1", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.1.tgz", + "integrity": "sha512-RmTOkE7hRU3OVREqFVITWHz6ocgBjv08GoePscAakgVQfciA3SGCEk7mb9IzwW61cKKmlTpHXG6DUE5Ubx+MGQ==", "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -1948,14 +446,394 @@ "w3c-keyname": "^2.2.4" } }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "dev": true, - "peer": true, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=10.0.0" + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -1998,9 +876,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", - "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "dependencies": { "@eslint/object-schema": "^2.1.6", @@ -2012,18 +890,18 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", - "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/core": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", - "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.15" @@ -2055,22 +933,10 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.32.0.tgz", + "integrity": "sha512-BBpRFZK3eX6uMLKz8WxFOBIFFcGFJ/g8XuwjTHCqHROSIsopI+ddn/d5Cfh36+7+e5edVS8dbSHnBNhrLEX0zg==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2089,33 +955,18 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.4.tgz", + "integrity": "sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==", "dev": true, "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "dev": true - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "dev": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2177,69 +1028,10 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "devOptional": true, - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "devOptional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "devOptional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "devOptional": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "devOptional": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "dev": true, - "peer": true + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", + "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==" }, "node_modules/@lezer/common": { "version": "1.2.3", @@ -2257,9 +1049,9 @@ } }, "node_modules/@lezer/css": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.1.tgz", - "integrity": "sha512-2F5tOqzKEKbCUNraIXc0f6HKeyKlmMWJnBB0i4XW6dJgssrZO/YlZ2pY5xgyqDleqqhiNJ3dQhbrV2aClZQMvg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", @@ -2342,9 +1134,9 @@ } }, "node_modules/@lezer/php": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.2.tgz", - "integrity": "sha512-GN7BnqtGRpFyeoKSEqxvGvhJQiI4zkgmYnDk/JIyc7H7Ifc1tkPnUn/R2R8meH3h/aBf5rzjvU8ZQoyiNDtDrA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.4.tgz", + "integrity": "sha512-D2dJ0t8Z28/G1guztRczMFvPDUqzeMLSQbdWQmaiHV7urc8NlEOnjYk9UrZ531OcLiRxD4Ihcbv7AsDpNKDRaQ==", "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", @@ -2411,190 +1203,251 @@ "resolved": "https://registry.npmjs.org/@mdi/font/-/font-7.4.47.tgz", "integrity": "sha512-43MtGpd585SNzHZPcYowu/84Vz2a2g31TvPMTm9uTiCSWzaheQySUcSyUH/46fPnuPQWof2yd0pGBtzee/IQWw==" }, - "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { - "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", - "dev": true, - "dependencies": { - "eslint-scope": "5.1.1" - } - }, - "node_modules/@node-ipc/js-queue": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@node-ipc/js-queue/-/js-queue-2.0.3.tgz", - "integrity": "sha512-fL1wpr8hhD5gT2dA1qifeVaoDFlQR5es8tFuKqjHX+kdOtdNHnxkVZbtIrR2rxnMFvehkjaZRNV2H/gPXlb0hw==", - "dev": true, - "dependencies": { - "easy-stack": "1.0.1" - }, - "engines": { - "node": ">=1.0.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@polka/url": { - "version": "1.0.0-next.29", - "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", - "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", - "dev": true, - "peer": true - }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "dev": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.29", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.29.tgz", + "integrity": "sha512-NIJgOsMjbxAXvoGq/X0gD7VPMQ8j9g0BiDaNjVNVjvl+iKXxL3Jre0v31RmBYeLEmkbj2s02v8vFTbUXi5XS2Q==", "dev": true }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "dev": true + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.46.2.tgz", + "integrity": "sha512-Zj3Hl6sN34xJtMv7Anwb5Gu01yujyE/cLBDB2gnHTAHaWS1Z38L7kuSG+oAh0giZMqG060f/YBStXtMH6FvPMA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@soda/friendly-errors-webpack-plugin": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz", - "integrity": "sha512-h2ooWqP8XuFqTXT+NyAFbrArzfQA7R6HTezADrvD9Re8fxMLTPPniLdqVTdDaO0eIoLaAwKT+d6w+5GeTk7Vbg==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^3.0.0", - "error-stack-parser": "^2.0.6", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.46.2.tgz", + "integrity": "sha512-nTeCWY83kN64oQ5MGz3CgtPx8NSOhC5lWtsjTs+8JAJNLcP3QbLCtDDgUKQc/Ro/frpMq4SHUaHN6AMltcEoLQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@soda/get-current-script": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@soda/get-current-script/-/get-current-script-1.0.2.tgz", - "integrity": "sha512-T7VNNlYVM1SgQ+VsMYhnDkcGmWhQdL0bDyGm5TlQ3GBXnJscEClUUOKduWTmm2zCnvNLC1hc3JpuXjs/nFOc5w==", - "dev": true, - "peer": true + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.46.2.tgz", + "integrity": "sha512-HV7bW2Fb/F5KPdM/9bApunQh68YVDU8sO8BvcW9OngQVN3HHHkw99wFupuUJfGR9pYLLAjcAOA6iO+evsbBaPQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10.13.0" - } + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.46.2.tgz", + "integrity": "sha512-SSj8TlYV5nJixSsm/y3QXfhspSiLYP11zpfwp6G/YDXctf3Xkdnk4woJIF5VQe0of2OjzTt8EsxnJDCdHd2xMA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@types/body-parser": { - "version": "1.19.5", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", - "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.46.2.tgz", + "integrity": "sha512-ZyrsG4TIT9xnOlLsSSi9w/X29tCbK1yegE49RYm3tu3wF1L/B6LVMqnEWyDB26d9Ecx9zrmXCiPmIabVuLmNSg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.46.2.tgz", + "integrity": "sha512-pCgHFoOECwVCJ5GFq8+gR8SBKnMO+xe5UEqbemxBpCKYQddRQMgomv1104RnLSg7nNvgKy05sLsY51+OVRyiVw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.46.2.tgz", + "integrity": "sha512-EtP8aquZ0xQg0ETFcxUbU71MZlHaw9MChwrQzatiE8U/bvi5uv/oChExXC4mWhjiqK7azGJBqU0tt5H123SzVA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.46.2.tgz", + "integrity": "sha512-qO7F7U3u1nfxYRPM8HqFtLd+raev2K137dsV08q/LRKRLEc7RsiDWihUnrINdsWQxPR9jqZ8DIIZ1zJJAm5PjQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "devOptional": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.46.2.tgz", + "integrity": "sha512-3dRaqLfcOXYsfvw5xMrxAk9Lb1f395gkoBYzSFcc/scgRFptRXL9DOaDpMiehf9CO8ZDRJW2z45b6fpU5nwjng==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "devOptional": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.46.2.tgz", + "integrity": "sha512-fhHFTutA7SM+IrR6lIfiHskxmpmPTJUXpWIsBXpeEwNgZzZZSg/q4i6FU4J8qOGyJ0TR+wXBwx/L7Ho9z0+uDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.46.2.tgz", + "integrity": "sha512-i7wfGFXu8x4+FRqPymzjD+Hyav8l95UIZ773j7J7zRYc3Xsxy2wIn4x+llpunexXe6laaO72iEjeeGyUFmjKeA==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.46.2.tgz", + "integrity": "sha512-B/l0dFcHVUnqcGZWKcWBSV2PF01YUt0Rvlurci5P+neqY/yMKchGU8ullZvIv5e8Y1C6wOn+U03mrDylP5q9Yw==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.46.2.tgz", + "integrity": "sha512-32k4ENb5ygtkMwPMucAb8MtV8olkPT03oiTxJbgkJa7lJ7dZMr0GCFJlyvy+K8iq7F/iuOr41ZdUHaOiqyR3iQ==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.46.2.tgz", + "integrity": "sha512-t5B2loThlFEauloaQkZg9gxV05BYeITLvLkWOkRXogP4qHXLkWSbSHKM9S6H1schf/0YGP/qNKtiISlxvfmmZw==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.46.2.tgz", + "integrity": "sha512-YKjekwTEKgbB7n17gmODSmJVUIvj8CX7q5442/CK80L8nqOUbMtf8b01QkG3jOqyr1rotrAnW6B/qiHwfcuWQA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.2.tgz", + "integrity": "sha512-Jj5a9RUoe5ra+MEyERkDKLwTXVu6s3aACP51nkfnK9wJTraCC8IMe3snOfALkrjTYd2G1ViE1hICj0fZ7ALBPA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.46.2.tgz", + "integrity": "sha512-7kX69DIrBeD7yNp4A5b81izs8BqoZkCIaxQaOpumcJ1S/kmqNFjPhDu1LHeVXv0SexfHQv5cqHsxLOjETuqDuA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.46.2.tgz", + "integrity": "sha512-wiJWMIpeaak/jsbaq2HMh/rzZxHVW1rU6coyeNNpMwk5isiPjSTx0a4YLSlYDwBH/WBvLz+EtsNqQScZTLJy3g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.46.2.tgz", + "integrity": "sha512-gBgaUDESVzMgWZhcyjfs9QFK16D8K6QZpwAaVNJxYDLHWayOta4ZMjGm/vsAEy3hvlS2GosVFlBlP9/Wb85DqQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.46.2.tgz", + "integrity": "sha512-CvUo2ixeIQGtF6WvuB87XWqPQkoFAFqW+HUo/WzHwuHDvIwZCtjdWXoYCcr06iKGydiqTclC4jU/TNObC/xKZg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@types/estree": { "version": "1.0.8", @@ -2602,843 +1455,123 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "devOptional": true }, - "node_modules/@types/express": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", - "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", - "dev": true, - "peer": true, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/express/node_modules/@types/express-serve-static-core": { - "version": "4.19.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", - "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "dev": true, - "peer": true - }, - "node_modules/@types/http-errors": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", - "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", - "dev": true, - "peer": true - }, - "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "devOptional": true - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true, - "peer": true - }, - "node_modules/@types/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==", - "dev": true, - "peer": true - }, - "node_modules/@types/node": { - "version": "22.15.30", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.30.tgz", - "integrity": "sha512-6Q7lr06bEHdlfplU6YRbgG1SFBdlsfNC4/lX+SkhiTs0cpJkOElmWls8PxDFv4yY/xKb8Y6SO0OmSX4wgqTZbA==", - "devOptional": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.11", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", - "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "dev": true, - "peer": true - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true, - "peer": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true, - "peer": true - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", - "dev": true, - "peer": true - }, - "node_modules/@types/send": { - "version": "0.17.4", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", - "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "dev": true, - "peer": true, - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.7", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", - "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", - "dev": true, - "peer": true, - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@vue/babel-helper-vue-jsx-merge-props": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.4.0.tgz", - "integrity": "sha512-JkqXfCkUDp4PIlFdDQ0TdXoIejMtTHP67/pvxlgeY+u5k3LEdKuWZ3LK6xkxo52uDoABIVyRwqVkfLQJhk7VBA==", - "dev": true - }, - "node_modules/@vue/babel-helper-vue-transform-on": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.4.0.tgz", - "integrity": "sha512-mCokbouEQ/ocRce/FpKCRItGo+013tHg7tixg3DUNS+6bmIchPt66012kBMm476vyEIJPafrvOf4E5OYj3shSw==", - "dev": true - }, - "node_modules/@vue/babel-plugin-jsx": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.4.0.tgz", - "integrity": "sha512-9zAHmwgMWlaN6qRKdrg1uKsBKHvnUU+Py+MOCTuYZBoZsopa90Di10QRjB+YPnVss0BZbG/H5XFwJY1fTxJWhA==", + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.1.tgz", + "integrity": "sha512-+MaE752hU0wfPFJEUAIxqw18+20euHHdxVtMvbFcOEpjEyfqXH/5DCoTHiVJ0J29EhTJdoTkjEv5YBKU9dnoTw==", "dev": true, "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/plugin-syntax-jsx": "^7.25.9", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.9", - "@babel/types": "^7.26.9", - "@vue/babel-helper-vue-transform-on": "1.4.0", - "@vue/babel-plugin-resolve-type": "1.4.0", - "@vue/shared": "^3.5.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - } - } - }, - "node_modules/@vue/babel-plugin-resolve-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.4.0.tgz", - "integrity": "sha512-4xqDRRbQQEWHQyjlYSgZsWj44KfiF6D+ktCuXyZ8EnVDYV3pztmXJDf1HveAjUAXxAnR8daCQT51RneWWxtTyQ==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-plugin-utils": "^7.26.5", - "@babel/parser": "^7.26.9", - "@vue/compiler-sfc": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-plugin-transform-vue-jsx": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-plugin-transform-vue-jsx/-/babel-plugin-transform-vue-jsx-1.4.0.tgz", - "integrity": "sha512-Fmastxw4MMx0vlgLS4XBX0XiBbUFzoMGeVXuMV08wyOfXdikAFqBTuYPR0tlk+XskL19EzHc39SgjrPGY23JnA==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.2.0", - "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", - "html-tags": "^2.0.0", - "lodash.kebabcase": "^4.1.1", - "svg-tags": "^1.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-preset-app": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-5.0.8.tgz", - "integrity": "sha512-yl+5qhpjd8e1G4cMXfORkkBlvtPCIgmRf3IYCWYDKIQ7m+PPa5iTm4feiNmCMD6yGqQWMhhK/7M3oWGL9boKwg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.16", - "@babel/helper-compilation-targets": "^7.12.16", - "@babel/helper-module-imports": "^7.12.13", - "@babel/plugin-proposal-class-properties": "^7.12.13", - "@babel/plugin-proposal-decorators": "^7.12.13", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-jsx": "^7.12.13", - "@babel/plugin-transform-runtime": "^7.12.15", - "@babel/preset-env": "^7.12.16", - "@babel/runtime": "^7.12.13", - "@vue/babel-plugin-jsx": "^1.0.3", - "@vue/babel-preset-jsx": "^1.1.2", - "babel-plugin-dynamic-import-node": "^2.3.3", - "core-js": "^3.8.3", - "core-js-compat": "^3.8.3", - "semver": "^7.3.4" - }, - "peerDependencies": { - "@babel/core": "*", - "core-js": "^3", - "vue": "^2 || ^3.2.13" - }, - "peerDependenciesMeta": { - "core-js": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/@vue/babel-preset-app/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" + "@rolldown/pluginutils": "1.0.0-beta.29" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@vue/babel-preset-jsx": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-preset-jsx/-/babel-preset-jsx-1.4.0.tgz", - "integrity": "sha512-QmfRpssBOPZWL5xw7fOuHNifCQcNQC1PrOo/4fu6xlhlKJJKSA3HqX92Nvgyx8fqHZTUGMPHmFA+IDqwXlqkSA==", - "dev": true, - "dependencies": { - "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", - "@vue/babel-plugin-transform-vue-jsx": "^1.4.0", - "@vue/babel-sugar-composition-api-inject-h": "^1.4.0", - "@vue/babel-sugar-composition-api-render-instance": "^1.4.0", - "@vue/babel-sugar-functional-vue": "^1.4.0", - "@vue/babel-sugar-inject-h": "^1.4.0", - "@vue/babel-sugar-v-model": "^1.4.0", - "@vue/babel-sugar-v-on": "^1.4.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "@babel/core": "^7.0.0-0", - "vue": "*" - }, - "peerDependenciesMeta": { - "vue": { - "optional": true - } + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", + "vue": "^3.2.25" } }, - "node_modules/@vue/babel-sugar-composition-api-inject-h": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-sugar-composition-api-inject-h/-/babel-sugar-composition-api-inject-h-1.4.0.tgz", - "integrity": "sha512-VQq6zEddJHctnG4w3TfmlVp5FzDavUSut/DwR0xVoe/mJKXyMcsIibL42wPntozITEoY90aBV0/1d2KjxHU52g==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-composition-api-render-instance": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-sugar-composition-api-render-instance/-/babel-sugar-composition-api-render-instance-1.4.0.tgz", - "integrity": "sha512-6ZDAzcxvy7VcnCjNdHJ59mwK02ZFuP5CnucloidqlZwVQv5CQLijc3lGpR7MD3TWFi78J7+a8J56YxbCtHgT9Q==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-functional-vue": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-sugar-functional-vue/-/babel-sugar-functional-vue-1.4.0.tgz", - "integrity": "sha512-lTEB4WUFNzYt2In6JsoF9sAYVTo84wC4e+PoZWSgM6FUtqRJz7wMylaEhSRgG71YF+wfLD6cc9nqVeXN2rwBvw==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-inject-h": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-sugar-inject-h/-/babel-sugar-inject-h-1.4.0.tgz", - "integrity": "sha512-muwWrPKli77uO2fFM7eA3G1lAGnERuSz2NgAxuOLzrsTlQl8W4G+wwbM4nB6iewlKbwKRae3nL03UaF5ffAPMA==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-v-model": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-sugar-v-model/-/babel-sugar-v-model-1.4.0.tgz", - "integrity": "sha512-0t4HGgXb7WHYLBciZzN5s0Hzqan4Ue+p/3FdQdcaHAb7s5D9WZFGoSxEZHrR1TFVZlAPu1bejTKGeAzaaG3NCQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0", - "@vue/babel-helper-vue-jsx-merge-props": "^1.4.0", - "@vue/babel-plugin-transform-vue-jsx": "^1.4.0", - "camelcase": "^5.0.0", - "html-tags": "^2.0.0", - "svg-tags": "^1.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/babel-sugar-v-on": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@vue/babel-sugar-v-on/-/babel-sugar-v-on-1.4.0.tgz", - "integrity": "sha512-m+zud4wKLzSKgQrWwhqRObWzmTuyzl6vOP7024lrpeJM4x2UhQtRDLgYjXAw9xBXjCwS0pP9kXjg91F9ZNo9JA==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-jsx": "^7.2.0", - "@vue/babel-plugin-transform-vue-jsx": "^1.4.0", - "camelcase": "^5.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@vue/cli-overlay": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@vue/cli-overlay/-/cli-overlay-5.0.8.tgz", - "integrity": "sha512-KmtievE/B4kcXp6SuM2gzsnSd8WebkQpg3XaB6GmFh1BJGRqa1UiW9up7L/Q67uOdTigHxr5Ar2lZms4RcDjwQ==", - "dev": true, - "peer": true - }, - "node_modules/@vue/cli-plugin-babel": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@vue/cli-plugin-babel/-/cli-plugin-babel-5.0.8.tgz", - "integrity": "sha512-a4qqkml3FAJ3auqB2kN2EMPocb/iu0ykeELwed+9B1c1nQ1HKgslKMHMPavYx3Cd/QAx2mBD4hwKBqZXEI/CsQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.12.16", - "@vue/babel-preset-app": "^5.0.8", - "@vue/cli-shared-utils": "^5.0.8", - "babel-loader": "^8.2.2", - "thread-loader": "^3.0.0", - "webpack": "^5.54.0" - }, - "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" - } - }, - "node_modules/@vue/cli-plugin-router": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@vue/cli-plugin-router/-/cli-plugin-router-5.0.8.tgz", - "integrity": "sha512-Gmv4dsGdAsWPqVijz3Ux2OS2HkMrWi1ENj2cYL75nUeL+Xj5HEstSqdtfZ0b1q9NCce+BFB6QnHfTBXc/fCvMg==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/cli-shared-utils": "^5.0.8" - }, - "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" - } - }, - "node_modules/@vue/cli-plugin-vuex": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz", - "integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==", - "dev": true, - "peer": true, - "peerDependencies": { - "@vue/cli-service": "^3.0.0 || ^4.0.0 || ^5.0.0-0" - } - }, - "node_modules/@vue/cli-service": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@vue/cli-service/-/cli-service-5.0.8.tgz", - "integrity": "sha512-nV7tYQLe7YsTtzFrfOMIHc5N2hp5lHG2rpYr0aNja9rNljdgcPZLyQRb2YRivTHqTv7lI962UXFURcpStHgyFw==", - "dev": true, - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.12.16", - "@soda/friendly-errors-webpack-plugin": "^1.8.0", - "@soda/get-current-script": "^1.0.2", - "@types/minimist": "^1.2.0", - "@vue/cli-overlay": "^5.0.8", - "@vue/cli-plugin-router": "^5.0.8", - "@vue/cli-plugin-vuex": "^5.0.8", - "@vue/cli-shared-utils": "^5.0.8", - "@vue/component-compiler-utils": "^3.3.0", - "@vue/vue-loader-v15": "npm:vue-loader@^15.9.7", - "@vue/web-component-wrapper": "^1.3.0", - "acorn": "^8.0.5", - "acorn-walk": "^8.0.2", - "address": "^1.1.2", - "autoprefixer": "^10.2.4", - "browserslist": "^4.16.3", - "case-sensitive-paths-webpack-plugin": "^2.3.0", - "cli-highlight": "^2.1.10", - "clipboardy": "^2.3.0", - "cliui": "^7.0.4", - "copy-webpack-plugin": "^9.0.1", - "css-loader": "^6.5.0", - "css-minimizer-webpack-plugin": "^3.0.2", - "cssnano": "^5.0.0", - "debug": "^4.1.1", - "default-gateway": "^6.0.3", - "dotenv": "^10.0.0", - "dotenv-expand": "^5.1.0", - "fs-extra": "^9.1.0", - "globby": "^11.0.2", - "hash-sum": "^2.0.0", - "html-webpack-plugin": "^5.1.0", - "is-file-esm": "^1.0.0", - "launch-editor-middleware": "^2.2.1", - "lodash.defaultsdeep": "^4.6.1", - "lodash.mapvalues": "^4.6.0", - "mini-css-extract-plugin": "^2.5.3", - "minimist": "^1.2.5", - "module-alias": "^2.2.2", - "portfinder": "^1.0.26", - "postcss": "^8.2.6", - "postcss-loader": "^6.1.1", - "progress-webpack-plugin": "^1.0.12", - "ssri": "^8.0.1", - "terser-webpack-plugin": "^5.1.1", - "thread-loader": "^3.0.0", - "vue-loader": "^17.0.0", - "vue-style-loader": "^4.1.3", - "webpack": "^5.54.0", - "webpack-bundle-analyzer": "^4.4.0", - "webpack-chain": "^6.5.1", - "webpack-dev-server": "^4.7.3", - "webpack-merge": "^5.7.3", - "webpack-virtual-modules": "^0.4.2", - "whatwg-fetch": "^3.6.2" - }, - "bin": { - "vue-cli-service": "bin/vue-cli-service.js" - }, - "engines": { - "node": "^12.0.0 || >= 14.0.0" - }, - "peerDependencies": { - "vue-template-compiler": "^2.0.0", - "webpack-sources": "*" - }, - "peerDependenciesMeta": { - "cache-loader": { - "optional": true - }, - "less-loader": { - "optional": true - }, - "pug-plain-loader": { - "optional": true - }, - "raw-loader": { - "optional": true - }, - "sass-loader": { - "optional": true - }, - "stylus-loader": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - }, - "webpack-sources": { - "optional": true - } - } - }, - "node_modules/@vue/cli-shared-utils": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz", - "integrity": "sha512-uK2YB7bBVuQhjOJF+O52P9yFMXeJVj7ozqJkwYE9PlMHL1LMHjtCYm4cSdOebuPzyP+/9p0BimM/OqxsevIopQ==", - "dev": true, - "dependencies": { - "@achrinza/node-ipc": "^9.2.5", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "joi": "^17.4.0", - "launch-editor": "^2.2.1", - "lru-cache": "^6.0.0", - "node-fetch": "^2.6.7", - "open": "^8.0.2", - "ora": "^5.3.0", - "read-pkg": "^5.1.1", - "semver": "^7.3.4", - "strip-ansi": "^6.0.0" - } - }, - "node_modules/@vue/cli-shared-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@vue/cli-shared-utils/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vue/cli-shared-utils/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@vue/cli-shared-utils/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/@vue/compiler-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz", - "integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.18.tgz", + "integrity": "sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.28.0", + "@vue/shared": "3.5.18", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz", - "integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.18.tgz", + "integrity": "sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==", "dependencies": { - "@vue/compiler-core": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-core": "3.5.18", + "@vue/shared": "3.5.18" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz", - "integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.18.tgz", + "integrity": "sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==", "dependencies": { - "@babel/parser": "^7.27.2", - "@vue/compiler-core": "3.5.16", - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16", + "@babel/parser": "^7.28.0", + "@vue/compiler-core": "3.5.18", + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18", "estree-walker": "^2.0.2", "magic-string": "^0.30.17", - "postcss": "^8.5.3", + "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz", - "integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.18.tgz", + "integrity": "sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.18", + "@vue/shared": "3.5.18" } }, - "node_modules/@vue/component-compiler-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.3.0.tgz", - "integrity": "sha512-97sfH2mYNU+2PzGrmK2haqffDpVASuib9/w2/noxiFi31Z54hW+q3izKQXXQZSNhtiUpAI36uSuYepeBe4wpHQ==", - "dev": true, - "peer": true, - "dependencies": { - "consolidate": "^0.15.1", - "hash-sum": "^1.0.2", - "lru-cache": "^4.1.2", - "merge-source-map": "^1.1.0", - "postcss": "^7.0.36", - "postcss-selector-parser": "^6.0.2", - "source-map": "~0.6.1", - "vue-template-es2015-compiler": "^1.9.0" - }, - "optionalDependencies": { - "prettier": "^1.18.2 || ^2.0.0" - } - }, - "node_modules/@vue/component-compiler-utils/node_modules/hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true, - "peer": true - }, - "node_modules/@vue/component-compiler-utils/node_modules/lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "peer": true, - "dependencies": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "node_modules/@vue/component-compiler-utils/node_modules/picocolors": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", - "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", - "dev": true, - "peer": true - }, - "node_modules/@vue/component-compiler-utils/node_modules/postcss": { - "version": "7.0.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", - "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", - "dev": true, - "peer": true, - "dependencies": { - "picocolors": "^0.2.1", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - } - }, - "node_modules/@vue/component-compiler-utils/node_modules/yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", - "dev": true, - "peer": true - }, "node_modules/@vue/reactivity": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz", - "integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.18.tgz", + "integrity": "sha512-x0vPO5Imw+3sChLM5Y+B6G1zPjwdOri9e8V21NnTnlEvkxatHEH5B5KEAJcjuzQ7BsjGrKtfzuQ5eQwXh8HXBg==", "dependencies": { - "@vue/shared": "3.5.16" + "@vue/shared": "3.5.18" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz", - "integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.18.tgz", + "integrity": "sha512-DUpHa1HpeOQEt6+3nheUfqVXRog2kivkXHUhoqJiKR33SO4x+a5uNOMkV487WPerQkL0vUuRvq/7JhRgLW3S+w==", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/reactivity": "3.5.18", + "@vue/shared": "3.5.18" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz", - "integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.18.tgz", + "integrity": "sha512-YwDj71iV05j4RnzZnZtGaXwPoUWeRsqinblgVJwR8XTXYZ9D5PbahHQgsbmzUvCWNF6x7siQ89HgnX5eWkr3mw==", "dependencies": { - "@vue/reactivity": "3.5.16", - "@vue/runtime-core": "3.5.16", - "@vue/shared": "3.5.16", + "@vue/reactivity": "3.5.18", + "@vue/runtime-core": "3.5.18", + "@vue/shared": "3.5.18", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz", - "integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.18.tgz", + "integrity": "sha512-PvIHLUoWgSbDG7zLHqSqaCoZvHi6NNmfVFOqO+OnwvqMz/tqQr3FuGWS8ufluNddk7ZLBJYMrjcw1c6XzR12mA==", "dependencies": { - "@vue/compiler-ssr": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-ssr": "3.5.18", + "@vue/shared": "3.5.18" }, "peerDependencies": { - "vue": "3.5.16" + "vue": "3.5.18" } }, "node_modules/@vue/shared": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz", - "integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==" - }, - "node_modules/@vue/vue-loader-v15": { - "name": "vue-loader", - "version": "15.11.1", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.11.1.tgz", - "integrity": "sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==", - "dev": true, - "peer": true, - "dependencies": { - "@vue/component-compiler-utils": "^3.1.0", - "hash-sum": "^1.0.2", - "loader-utils": "^1.1.0", - "vue-hot-reload-api": "^2.3.0", - "vue-style-loader": "^4.1.0" - }, - "peerDependencies": { - "css-loader": "*", - "webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0" - }, - "peerDependenciesMeta": { - "cache-loader": { - "optional": true - }, - "prettier": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/@vue/vue-loader-v15/node_modules/hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true, - "peer": true - }, - "node_modules/@vue/web-component-wrapper": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", - "integrity": "sha512-Iu8Tbg3f+emIIMmI2ycSI8QcEuAUgPTgHwesDU1eKMLE4YC/c/sFbGc70QgMq31ijRftV0R7vCm9co6rldCeOA==", - "dev": true, - "peer": true + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.18.tgz", + "integrity": "sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==" }, "node_modules/@vuetify/loader-shared": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.0.tgz", - "integrity": "sha512-dNE6Ceym9ijFsmJKB7YGW0cxs7xbYV8+1LjU6jd4P14xOt/ji4Igtgzt0rJFbxu+ZhAzqz853lhB0z8V9Dy9cQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@vuetify/loader-shared/-/loader-shared-2.1.1.tgz", + "integrity": "sha512-jSZTzTYaoiv8iwonFCVZQ0YYX/M+Uyl4ng+C4egMJT0Hcmh9gIxJL89qfZICDeo3g0IhqrvipW2FFKKRDMtVcA==", "devOptional": true, "dependencies": { "upath": "^2.0.1" @@ -3448,193 +1581,11 @@ "vuetify": "^3.0.0" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "devOptional": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "devOptional": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "devOptional": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "devOptional": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "devOptional": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "devOptional": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "devOptional": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "devOptional": true, - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "devOptional": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "devOptional": true - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dev": true, - "peer": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "devOptional": true, + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -3651,34 +1602,11 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "peer": true, - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "devOptional": true, + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -3690,86 +1618,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "devOptional": true, - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "devOptional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "devOptional": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "devOptional": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "peer": true, - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3785,333 +1633,18 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "peer": true - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "peer": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "dev": true, - "peer": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "peer": true - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.21", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "peer": true, - "dependencies": { - "browserslist": "^4.24.4", - "caniuse-lite": "^1.0.30001702", - "fraction.js": "^4.3.7", - "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/babel-loader": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.4.1.tgz", - "integrity": "sha512-nXzRChX+Z1GoE6yWavBQg6jDslyFF3SDjl2paADuoQtQW10JqShJt62R6eJQ5m/pjJFDT8xgKIWSP85OY8eXeA==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.4", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", - "dev": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true, - "peer": true - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "devOptional": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true, - "peer": true - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "dev": true, - "peer": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -4128,147 +1661,6 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "peer": true, - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha512-0vdNRFXn5q+dtOqjfFtmtlI9N2eVZ7LMyEV2iKC5mEEFvSg/69Ml6b/WU2qF8W1nLRa0wiSrDT3Y5jOHZCwKPQ==", - "devOptional": true, - "engines": { - "node": "*" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -4278,183 +1670,11 @@ "node": ">=6" } }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "dev": true, - "peer": true, - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/case-sensitive-paths-webpack-plugin": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", - "integrity": "sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, "node_modules/chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "peer": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "devOptional": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "dev": true, - "peer": true, - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "peer": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -4466,73 +1686,10 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clipboardy": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-2.3.0.tgz", - "integrity": "sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==", - "dev": true, - "peer": true, - "dependencies": { - "arch": "^2.1.1", - "execa": "^1.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "peer": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/codemirror": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", - "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", "peer": true, "dependencies": { "@codemirror/autocomplete": "^6.0.0", @@ -4562,258 +1719,22 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true, - "peer": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "peer": true - }, - "node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "devOptional": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "dev": true, - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.0.tgz", - "integrity": "sha512-k6WLKfunuqCYD3t6AsuPGvQWaKwuLLh2/xHNcX4qE+vIfDNXpSqnrhwA7O53R7WVQUnt8dVAIW+YHr7xTgOgGA==", - "dev": true, - "peer": true, - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.0.2", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/consolidate": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", - "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", - "deprecated": "Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog", - "dev": true, - "peer": true, - "dependencies": { - "bluebird": "^3.1.1" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true, - "peer": true - }, - "node_modules/copy-webpack-plugin": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.1.0.tgz", - "integrity": "sha512-rxnR7PaGigJzhqETHGmAcxKnLZSR5u1Y3/bcIv/1FnqXedcL/E2ewK7ZCNrArJKCiSv8yVXhTqetJh8inDvfsA==", - "dev": true, - "peer": true, - "dependencies": { - "fast-glob": "^3.2.7", - "glob-parent": "^6.0.1", - "globby": "^11.0.3", - "normalize-path": "^3.0.0", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/core-js": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.42.0.tgz", - "integrity": "sha512-Sz4PP4ZA+Rq4II21qkNqOEDTDrCvcANId3xpIgB34NDkWc3UduWj2dqEtN9yZIq8Dk3HyPI33x9sqqU5C8sr0g==", + "version": "3.44.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.44.0.tgz", + "integrity": "sha512-aFCtd4l6GvAXwVEh3XbbVqJGHDJt0OZRa+5ePGx3LLwi12WfexqQxcsohb2wgsa/92xtl19Hd66G/L+TaAxDMw==", "hasInstallScript": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/core-js-compat": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", - "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", - "dev": true, - "dependencies": { - "browserslist": "^4.24.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "peer": true - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "dev": true, - "peer": true, - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -4833,208 +1754,6 @@ "node": ">= 8" } }, - "node_modules/css-declaration-sorter": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", - "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", - "dev": true, - "peer": true, - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-loader/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.4.1.tgz", - "integrity": "sha512-1u6D71zeIfgngN2XNRJefc/hY7Ybsxd74Jm4qngIXyUEk7fss3VUzuHxLAq/R8NAba4QU9OUSaMZlbpRc7bM4Q==", - "dev": true, - "peer": true, - "dependencies": { - "cssnano": "^5.0.6", - "jest-worker": "^27.0.2", - "postcss": "^8.3.5", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true - }, - "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "peer": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dev": true, - "peer": true, - "dependencies": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -5047,115 +1766,16 @@ "node": ">=4" } }, - "node_modules/cssnano": { - "version": "5.1.15", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", - "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", - "dev": true, - "peer": true, - "dependencies": { - "cssnano-preset-default": "^5.2.14", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-preset-default": { - "version": "5.2.14", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", - "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", - "dev": true, - "peer": true, - "dependencies": { - "css-declaration-sorter": "^6.3.1", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.1", - "postcss-convert-values": "^5.1.3", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.7", - "postcss-merge-rules": "^5.1.4", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.4", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.1", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.2", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dev": true, - "peer": true, - "dependencies": { - "css-tree": "^1.1.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "dev": true, - "peer": true - }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.1.3" }, @@ -5168,306 +1788,12 @@ } } }, - "node_modules/decache": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/decache/-/decache-4.6.2.tgz", - "integrity": "sha512-2LPqkLeu8XWHU8qNCS3kcF6sCcb5zIzvWaAHYSvPfwhdd7mHuah29NssMzrTYyHN4F5oFy2ko9OBYxegtU0FEw==", - "devOptional": true, - "dependencies": { - "callsite": "^1.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/deepmerge": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-1.5.2.tgz", - "integrity": "sha512-95k0GDqvBjZavkuvzx/YqVLv/6YYa17fz6ILMSf7neqQITCPbnfEnQvEgMPNjH4kgobe7+WIL0yJEHku+H3qtQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", - "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", - "dev": true, - "peer": true, - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/default-gateway/node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "peer": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/default-gateway/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-gateway/node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "peer": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true, - "peer": true - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "peer": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "dev": true, - "peer": true, - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "dev": true, - "peer": true, - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "peer": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "peer": true - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "peer": true, - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "peer": true, - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "dev": true, - "peer": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/dot-prop": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", @@ -5482,114 +1808,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/dotenv-expand": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", - "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", - "dev": true, - "peer": true - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true, - "peer": true - }, - "node_modules/easy-stack": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz", - "integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==", - "dev": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "dev": true, - "peer": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", - "devOptional": true - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "peer": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "devOptional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "devOptional": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -5601,77 +1819,47 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "dev": true, - "peer": true, - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "devOptional": true - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "dependencies": { - "es-errors": "^1.3.0" + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "devOptional": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.4" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "dev": true, - "peer": true - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -5685,19 +1873,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.32.0.tgz", + "integrity": "sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", - "@eslint/core": "^0.14.0", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.15.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", - "@eslint/plugin-kit": "^0.3.1", + "@eslint/js": "9.32.0", + "@eslint/plugin-kit": "^0.3.4", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -5708,9 +1896,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -5745,9 +1933,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.2.0.tgz", - "integrity": "sha512-tl9s+KN3z0hN2b8fV2xSs5ytGl7Esk1oSCxULLwFcdaElhZ8btYYZFrWxvh4En+czrSDtuLCeCOGa8HhEZuBdQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.4.0.tgz", + "integrity": "sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", @@ -5761,64 +1949,20 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" - } - }, - "node_modules/eslint-plugin-vue/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" }, - "engines": { - "node": ">=10" + "peerDependenciesMeta": { + "@typescript-eslint/parser": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "devOptional": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -5831,10 +1975,10 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5843,24 +1987,15 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/espree": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", - "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "dependencies": { - "acorn": "^8.14.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.0" + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5869,18 +2004,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/esquery": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", @@ -5893,20 +2016,11 @@ "node": ">=0.10" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "devOptional": true, + "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -5914,20 +2028,11 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "devOptional": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=4.0" } @@ -5946,231 +2051,17 @@ "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-pubsub": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz", - "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true, - "peer": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "devOptional": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "dev": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "peer": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "peer": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } + "dev": true }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "devOptional": true + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -6178,66 +2069,18 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true } - ] - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "peer": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "dev": true, - "peer": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", - "dev": true, - "peer": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" } }, "node_modules/file-entry-cache": { @@ -6252,124 +2095,6 @@ "node": ">=16.0.0" } }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "devOptional": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "devOptional": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "devOptional": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "peer": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "dev": true, - "peer": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6386,16 +2111,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "peer": true, - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -6415,203 +2130,19 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "peer": true, - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fraction.js": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "peer": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", - "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", - "dev": true, - "peer": true - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "peer": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -6624,452 +2155,27 @@ "node": ">=10.13.0" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "devOptional": true - }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "peer": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "devOptional": true - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "dev": true, - "peer": true, - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "dev": true, - "peer": true - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hash-sum": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", - "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", - "dev": true, - "peer": true - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "peer": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "dev": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "dev": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "peer": true - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/mdevils" - }, - { - "type": "patreon", - "url": "https://patreon.com/mdevils" - } - ], - "peer": true - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "peer": true - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "dev": true, - "peer": true, - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-tags": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-2.0.0.tgz", - "integrity": "sha512-+Il6N8cCo2wB/Vd3gqy/8TZhTD3QvcVeQLCnZiGkGCH3JP28IgGAY41giccp2W4R3jfyJPAP318FQTa1yU7K7g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.3.tgz", - "integrity": "sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==", - "dev": true, - "peer": true, - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "peer": true, - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "dev": true, - "peer": true - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "peer": true, - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "dev": true, - "peer": true - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, - "peer": true, - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "dev": true, - "peer": true, - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -7104,91 +2210,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "peer": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -7198,26 +2219,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-file-esm": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-file-esm/-/is-file-esm-1.0.0.tgz", - "integrity": "sha512-rZlaNKb4Mr8WlRu2A9XdeoKgnO5aA53XdPHgCKVyCrQ/rWi89RET1+bq37Ru46obaQXeiX4vmFIm1vks41hoSA==", - "dev": true, - "peer": true, - "dependencies": { - "read-pkg-up": "^7.0.1" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -7230,171 +2231,12 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "peer": true - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/javascript-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", - "integrity": "sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==", - "dev": true, - "peer": true - }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "dev": true, - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-message": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", - "integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==", - "dev": true, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -7407,41 +2249,17 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "devOptional": true - }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "devOptional": true + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -7449,31 +2267,6 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "devOptional": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "peer": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7483,46 +2276,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klona": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/klona/-/klona-2.0.6.tgz", - "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/launch-editor": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", - "integrity": "sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==", - "dev": true, - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.8.1" - } - }, - "node_modules/launch-editor-middleware": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.10.0.tgz", - "integrity": "sha512-RzZu7MeVlE3p1H6Sadc2BhuDGAj7bkeDCBpNq/zSENP4ohJGhso00k5+iYaRwKshIpiOAhMmimce+5D389xmSg==", - "dev": true, - "peer": true, - "dependencies": { - "launch-editor": "^2.10.0" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7536,64 +2289,11 @@ "node": ">= 0.8.0" } }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, "node_modules/litegraph.js": { "version": "0.7.18", "resolved": "https://registry.npmjs.org/litegraph.js/-/litegraph.js-0.7.18.tgz", "integrity": "sha512-1WEwjOO58j4FcLX8DvsuMXM371MEq4Y+8pBr3q2pBhJ9nDkwBtBd9Gj6bxArBKhW6i42bSOyv9ybeuez6NAxoQ==" }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "devOptional": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/loader-utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz", - "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==", - "dev": true, - "peer": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/loader-utils/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "peer": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7612,42 +2312,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true, - "peer": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true - }, - "node_modules/lodash.defaultsdeep": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", - "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==", - "dev": true, - "peer": true - }, - "node_modules/lodash.kebabcase": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", - "dev": true - }, - "node_modules/lodash.mapvalues": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", - "integrity": "sha512-JPFqXFeZQ7BfS00H58kClY7SPVeHertPE0lNuCyZ26/XlN8TvakYD7b9bGyNmXbT/D3BbtPAAmq90gPWqLkxlQ==", - "dev": true, - "peer": true - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true, - "peer": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.merge": { "version": "4.6.2", @@ -7655,190 +2320,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true, - "peer": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-update": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz", - "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-escapes": "^3.0.0", - "cli-cursor": "^2.0.0", - "wrap-ansi": "^3.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", - "dev": true, - "peer": true, - "dependencies": { - "restore-cursor": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", - "dev": true, - "peer": true, - "dependencies": { - "mimic-fn": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", - "dev": true, - "peer": true, - "dependencies": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "peer": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz", - "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==", - "dev": true, - "peer": true, - "dependencies": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "dev": true, - "peer": true, - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/magic-string": { "version": "0.30.17", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", @@ -7847,21 +2328,6 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/marked": { "version": "15.0.12", "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", @@ -7873,233 +2339,6 @@ "node": ">= 18" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true, - "peer": true - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", - "dev": true, - "peer": true, - "dependencies": { - "fs-monkey": "^1.0.4" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-source-map": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", - "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, - "peer": true, - "dependencies": { - "source-map": "^0.6.1" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "peer": true, - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "dev": true, - "peer": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", - "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", - "dev": true, - "peer": true, - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/mini-css-extract-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true - }, - "node_modules/mini-css-extract-plugin/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true, - "peer": true - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -8112,96 +2351,11 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "peer": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "peer": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, - "peer": true - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "devOptional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/module-alias": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", - "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", - "dev": true, - "peer": true - }, - "node_modules/mrmime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", - "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "dev": true, - "peer": true, - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "peer": true, - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } + "devOptional": true }, "node_modules/nanoid": { "version": "3.3.11", @@ -8226,150 +2380,6 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "devOptional": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "dev": true, - "peer": true, - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "devOptional": true - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -8382,191 +2392,6 @@ "url": "https://github.com/fb55/nth-check?sponsor=1" } }, - "node_modules/null-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", - "devOptional": true, - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/null-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "devOptional": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/null-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "devOptional": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "dev": true, - "peer": true - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dev": true, - "peer": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "dev": true, - "peer": true, - "bin": { - "opener": "bin/opener-bin.js" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -8584,54 +2409,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -8662,40 +2439,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-retry": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", - "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "dev": true, - "peer": true, - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8708,69 +2451,6 @@ "node": ">=6" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true, - "peer": true - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dev": true, - "peer": true, - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true, - "peer": true - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "dev": true, - "peer": true, - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8780,15 +2460,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8798,129 +2469,27 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "dev": true, - "peer": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "peer": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "devOptional": true, "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/portfinder": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.37.tgz", - "integrity": "sha512-yuGIEjDAYnnOex9ddMnKZEMFE0CcGo6zbfzDklkmT1m5z734ss6JMzN9rNB3+RR7iS+F10D4/BVIaXOyh8PQKw==", - "dev": true, - "peer": true, - "dependencies": { - "async": "^3.2.6", - "debug": "^4.3.6" - }, - "engines": { - "node": ">= 10.12" - } - }, "node_modules/postcss": { - "version": "8.5.4", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", - "integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -8944,532 +2513,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-colormin": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", - "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-convert-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", - "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-loader": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", - "integrity": "sha512-WbbYpmAaKcux/P66bZ40bpWsBucjx/TTgVVzRZ9yUO8yQfVBlameJ0ZGVaPfH64hNSBh63a+ICP5nqOpBA0w+Q==", - "dev": true, - "peer": true, - "dependencies": { - "cosmiconfig": "^7.0.0", - "klona": "^2.0.5", - "semver": "^7.3.5" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-loader/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", - "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.1" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-merge-rules": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", - "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "dev": true, - "peer": true, - "dependencies": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-params": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", - "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "dev": true, - "peer": true, - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "dev": true, - "peer": true, - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "dev": true, - "peer": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true, - "peer": true, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", - "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "dev": true, - "peer": true, - "dependencies": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "dev": true, - "peer": true, - "dependencies": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", - "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, "node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", @@ -9483,46 +2526,6 @@ "node": ">=4" } }, - "node_modules/postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "dev": true, - "peer": true, - "dependencies": { - "postcss-selector-parser": "^6.0.5" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "peer": true - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9532,541 +2535,15 @@ "node": ">= 0.8.0" } }, - "node_modules/prettier": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", - "dev": true, - "optional": true, - "peer": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "dev": true, - "peer": true, - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "peer": true - }, - "node_modules/progress-webpack-plugin": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/progress-webpack-plugin/-/progress-webpack-plugin-1.0.16.tgz", - "integrity": "sha512-sdiHuuKOzELcBANHfrupYo+r99iPRyOnw15qX+rNlVUqXGfjXdH4IgxriKwG1kNJwVswKQHMdj1hYZMcb9jFaA==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^2.1.0", - "figures": "^2.0.0", - "log-update": "^2.3.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "peerDependencies": { - "webpack": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0" - } - }, - "node_modules/progress-webpack-plugin/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/progress-webpack-plugin/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/progress-webpack-plugin/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/progress-webpack-plugin/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "peer": true - }, - "node_modules/progress-webpack-plugin/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/progress-webpack-plugin/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/progress-webpack-plugin/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dev": true, - "peer": true, - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", - "dev": true, - "peer": true - }, - "node_modules/pump": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", - "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dev": true, - "peer": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "devOptional": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dev": true, - "peer": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "peer": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg-up/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up/node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "peer": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", - "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, - "dependencies": { - "jsesc": "~3.0.2" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "dev": true, - "peer": true, - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "peer": true - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -10076,359 +2553,60 @@ "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "peer": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/roboto-fontface": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz", "integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g==" }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "peer": true, - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "node_modules/rollup": { + "version": "4.46.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.46.2.tgz", + "integrity": "sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==", "devOptional": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "peer": true - }, - "node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" }, "engines": { - "node": ">= 8.9.0" + "node": ">=18.0.0", + "npm": ">=8.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "dev": true, - "peer": true - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dev": true, - "peer": true, - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.46.2", + "@rollup/rollup-android-arm64": "4.46.2", + "@rollup/rollup-darwin-arm64": "4.46.2", + "@rollup/rollup-darwin-x64": "4.46.2", + "@rollup/rollup-freebsd-arm64": "4.46.2", + "@rollup/rollup-freebsd-x64": "4.46.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.46.2", + "@rollup/rollup-linux-arm-musleabihf": "4.46.2", + "@rollup/rollup-linux-arm64-gnu": "4.46.2", + "@rollup/rollup-linux-arm64-musl": "4.46.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.46.2", + "@rollup/rollup-linux-ppc64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-gnu": "4.46.2", + "@rollup/rollup-linux-riscv64-musl": "4.46.2", + "@rollup/rollup-linux-s390x-gnu": "4.46.2", + "@rollup/rollup-linux-x64-gnu": "4.46.2", + "@rollup/rollup-linux-x64-musl": "4.46.2", + "@rollup/rollup-win32-arm64-msvc": "4.46.2", + "@rollup/rollup-win32-ia32-msvc": "4.46.2", + "@rollup/rollup-win32-x64-msvc": "4.46.2", + "fsevents": "~2.3.2" } }, "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", "dev": true, "bin": { "semver": "bin/semver.js" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "peer": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "devOptional": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "dev": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "peer": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true, - "peer": true - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "peer": true - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true, - "peer": true - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "dev": true, - "peer": true, - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "dev": true, - "peer": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "peer": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" + "node": ">=10" } }, "node_modules/shebang-command": { @@ -10452,173 +2630,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/shelljs": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", - "dev": true, - "dependencies": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - }, - "bin": { - "shjs": "bin/shjs" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "peer": true, - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "peer": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "peer": true, - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "dev": true, - "peer": true, - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "dev": true, - "peer": true, - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -10627,173 +2638,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true - }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "dev": true, - "peer": true, - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "dev": true, - "peer": true, - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "peer": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility", - "dev": true, - "peer": true - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "dev": true, - "peer": true - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "peer": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -10811,23 +2655,6 @@ "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" }, - "node_modules/stylehacks": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", - "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", - "dev": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4", - "postcss-selector-parser": "^6.0.4" - }, - "engines": { - "node": "^10 || ^12 || >=14.0" - }, - "peerDependencies": { - "postcss": "^8.2.15" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10840,307 +2667,22 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svg-tags": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", - "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", - "dev": true - }, - "node_modules/svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "dev": true, - "peer": true, - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.41.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.41.0.tgz", - "integrity": "sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw==", + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", "devOptional": true, "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.14.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" + "fdir": "^6.4.4", + "picomatch": "^4.0.2" }, "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "devOptional": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" + "node": ">=12.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "devOptional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "devOptional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "devOptional": true - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "devOptional": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "peer": true, - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "peer": true, - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/thread-loader": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/thread-loader/-/thread-loader-3.0.4.tgz", - "integrity": "sha512-ByaL2TPb+m6yArpqQUZvP+5S1mZtXsEP7nWKKlAUTm7fCml8kB5s1uI3+eHRP2bk5mVYfRSBI7FFf+tWEyLZwA==", - "dev": true, - "dependencies": { - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^4.1.0", - "loader-utils": "^2.0.0", - "neo-async": "^2.6.2", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.27.0 || ^5.0.0" - } - }, - "node_modules/thread-loader/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/thread-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "dev": true, - "peer": true - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "peer": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/totalist": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", - "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "peer": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11164,86 +2706,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, - "peer": true, - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "devOptional": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/upath": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/upath/-/upath-2.0.1.tgz", @@ -11254,41 +2716,11 @@ "yarn": "*" } }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "devOptional": true, + "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -11299,23 +2731,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "dev": true, - "peer": true - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", @@ -11328,36 +2743,109 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, + "node_modules/vite": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.6.tgz", + "integrity": "sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==", + "devOptional": true, "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } } }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, - "peer": true, + "node_modules/vite-plugin-vuetify": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/vite-plugin-vuetify/-/vite-plugin-vuetify-2.1.2.tgz", + "integrity": "sha512-I/wd6QS+DO6lHmuGoi1UTyvvBTQ2KDzQZ9oowJQEJ6OcjWfJnscYXx2ptm6S7fJSASuZT8jGRBL3LV4oS3LpaA==", + "devOptional": true, + "dependencies": { + "@vuetify/loader-shared": "^2.1.1", + "debug": "^4.3.3", + "upath": "^2.0.1" + }, "engines": { - "node": ">= 0.8" + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": ">=5", + "vue": "^3.0.0", + "vuetify": "^3.0.0" } }, "node_modules/vue": { - "version": "3.5.16", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz", - "integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==", + "version": "3.5.18", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.18.tgz", + "integrity": "sha512-7W4Y4ZbMiQ3SEo+m9lnoNpV9xG7QVMLa+/0RFwwiAVkeYoyGXqWE85jabU4pllJNUzqfLShJ5YLptewhCWUgNA==", "dependencies": { - "@vue/compiler-dom": "3.5.16", - "@vue/compiler-sfc": "3.5.16", - "@vue/runtime-dom": "3.5.16", - "@vue/server-renderer": "3.5.16", - "@vue/shared": "3.5.16" + "@vue/compiler-dom": "3.5.18", + "@vue/compiler-sfc": "3.5.18", + "@vue/runtime-dom": "3.5.18", + "@vue/server-renderer": "3.5.18", + "@vue/shared": "3.5.18" }, "peerDependencies": { "typescript": "*" @@ -11368,40 +2856,6 @@ } } }, - "node_modules/vue-cli-plugin-vuetify": { - "version": "2.5.8", - "resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.5.8.tgz", - "integrity": "sha512-uqi0/URJETJBbWlQHD1l0pnY7JN8Ytu+AL1fw50HFlGByPa8/xx+mq19GkFXA9FcwFT01IqEc/TkxMPugchomg==", - "dev": true, - "dependencies": { - "null-loader": "^4.0.1", - "semver": "^7.1.2", - "shelljs": "^0.8.3" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "sass-loader": { - "optional": true - }, - "vuetify-loader": { - "optional": true - } - } - }, - "node_modules/vue-cli-plugin-vuetify/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/vue-codemirror": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/vue-codemirror/-/vue-codemirror-6.1.1.tgz", @@ -11418,9 +2872,9 @@ } }, "node_modules/vue-eslint-parser": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", - "integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.2.0.tgz", + "integrity": "sha512-CydUvFOQKD928UzZhTp4pr2vWz1L+H99t7Pkln2QSPdvmURT0MoC4wUccfCnuEaihNsu9aYYyk+bep8rlfkUXw==", "dev": true, "peer": true, "dependencies": { @@ -11429,7 +2883,6 @@ "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.6.0", - "lodash": "^4.17.21", "semver": "^7.6.3" }, "engines": { @@ -11442,135 +2895,10 @@ "eslint": "^8.57.0 || ^9.0.0" } }, - "node_modules/vue-eslint-parser/node_modules/eslint-scope": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", - "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", - "dev": true, - "peer": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/eslint-visitor-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", - "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", - "dev": true, - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/vue-eslint-parser/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/vue-eslint-parser/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/vue-hot-reload-api": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", - "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", - "dev": true, - "peer": true - }, - "node_modules/vue-loader": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.4.2.tgz", - "integrity": "sha512-yTKOA4R/VN4jqjw4y5HrynFL8AK0Z3/Jt7eOJXEitsm0GMRHDBjCfCiuTiLP7OESvsZYo2pATCWhDqxC5ZrM6w==", - "dev": true, - "peer": true, - "dependencies": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "watchpack": "^2.4.0" - }, - "peerDependencies": { - "webpack": "^4.1.0 || ^5.0.0-0" - }, - "peerDependenciesMeta": { - "@vue/compiler-sfc": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/vue-loader/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/vue-style-loader": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.3.tgz", - "integrity": "sha512-sFuh0xfbtpRlKfm39ss/ikqs9AbKCoXZBpHeVZ8Tx650o0k0q/YCM7FRvigtxpACezfq6af+a7JeqVTWvncqDg==", - "dev": true, - "peer": true, - "dependencies": { - "hash-sum": "^1.0.2", - "loader-utils": "^1.0.2" - } - }, - "node_modules/vue-style-loader/node_modules/hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true, - "peer": true - }, - "node_modules/vue-template-es2015-compiler": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", - "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", - "dev": true, - "peer": true - }, "node_modules/vuetify": { - "version": "3.8.8", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.8.8.tgz", - "integrity": "sha512-EPFynvxh72PBgUVZnGpfYfGluz8dz/tXM1OzjszFOK7ywqS+bAm8K9jJq0MIlAG8HKE7gBFQwCJGkzIyuUDipA==", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.9.3.tgz", + "integrity": "sha512-0eruHdmRoAMBo/08RLDkTdtdu1vfkx+/PurUIDW2tz/k2GCp51e7KwgCn4uVyzH88KRgf2PKiz5UI5f93Xn05w==", "engines": { "node": "^12.20 || >=14.13" }, @@ -11601,646 +2929,11 @@ "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "devOptional": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "dev": true, - "peer": true, - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/webfontloader": { "version": "1.6.28", "resolved": "https://registry.npmjs.org/webfontloader/-/webfontloader-1.6.28.tgz", "integrity": "sha512-Egb0oFEga6f+nSgasH3E0M405Pzn6y3/9tOVanv/DLfa1YBIgcv90L18YyWnvXkRbIM17v5Kv6IT2N6g1x5tvQ==" }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/webpack": { - "version": "5.99.9", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.9.tgz", - "integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==", - "devOptional": true, - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", - "dev": true, - "peer": true, - "dependencies": { - "@discoveryjs/json-ext": "0.5.7", - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "gzip-size": "^6.0.0", - "html-escaper": "^2.0.2", - "opener": "^1.5.2", - "picocolors": "^1.0.0", - "sirv": "^2.0.3", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-chain": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/webpack-chain/-/webpack-chain-6.5.1.tgz", - "integrity": "sha512-7doO/SRtLu8q5WM0s7vPKPWX580qhi0/yBHkOxNkv50f6qB76Zy9o2wRTrrPULqYTvQlVHuvbA8v+G5ayuUDsA==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "dev": true, - "peer": true, - "dependencies": { - "deepmerge": "^1.5.2", - "javascript-stringify": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", - "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", - "dev": true, - "peer": true, - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true - }, - "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz", - "integrity": "sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==", - "dev": true, - "peer": true, - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.5", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.4", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack-dev-server/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "peer": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "peer": true - }, - "node_modules/webpack-dev-server/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "dev": true, - "peer": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/webpack-dev-server/node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "dev": true, - "peer": true, - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-plugin-vuetify": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/webpack-plugin-vuetify/-/webpack-plugin-vuetify-3.1.1.tgz", - "integrity": "sha512-z3e/MtOfqrsKFUGlXe1MepgBkXvmnWDr7L1nFLpl2AHLHMKf1/sL6kjj1mMnEWQ57dKIJb7ejnQsLKK4jnKWow==", - "devOptional": true, - "dependencies": { - "@vuetify/loader-shared": "^2.1.0", - "decache": "^4.6.0", - "file-loader": "^6.2.0", - "find-cache-dir": "^5.0.0", - "loader-utils": "^2.0.0", - "mkdirp": "^1.0.4", - "null-loader": "^4.0.1", - "upath": "^2.0.1" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "@vue/compiler-sfc": "^3.2.6", - "vue": "^3.2.6", - "vuetify": "^3.0.0", - "webpack": "^5.0.0" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/find-cache-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-5.0.0.tgz", - "integrity": "sha512-OuWNfjfP05JcpAP3JPgAKUhWefjMRfI5iAoSsvE24ANYWJaepAtlSgWECSVEuRgSXpyNEc9DJwG/TZpgcOqyig==", - "devOptional": true, - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "devOptional": true, - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "devOptional": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "devOptional": true, - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "devOptional": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "devOptional": true, - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "devOptional": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "devOptional": true, - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-plugin-vuetify/node_modules/yocto-queue": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", - "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", - "devOptional": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.2.tgz", - "integrity": "sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==", - "devOptional": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-virtual-modules": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.4.6.tgz", - "integrity": "sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==", - "dev": true, - "peer": true - }, - "node_modules/webpack/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "devOptional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "devOptional": true, - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/webpack/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "devOptional": true - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", - "devOptional": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "peer": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "peer": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.20", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", - "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "dev": true, - "peer": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -12256,13 +2949,6 @@ "node": ">= 8" } }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "dev": true, - "peer": true - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -12272,52 +2958,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xml-name-validator": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", @@ -12327,61 +2967,6 @@ "node": ">=12" } }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "peer": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "peer": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "peer": true, - "engines": { - "node": ">=10" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/talemate_frontend/package.json b/talemate_frontend/package.json index fd5dbbd6..372bf2b2 100644 --- a/talemate_frontend/package.json +++ b/talemate_frontend/package.json @@ -1,16 +1,17 @@ { "name": "talemate_frontend", - "version": "0.31.0", + "version": "0.32.0", "private": true, + "type": "module", "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint" + "serve": "vite", + "build": "vite build", + "lint": "eslint . --ext .vue,.js,.ts --fix" }, "dependencies": { "@codemirror/lang-json": "^6.0.1", - "@codemirror/lang-yaml": "^6.1.2", "@codemirror/lang-markdown": "^6.2.5", + "@codemirror/lang-yaml": "^6.1.2", "@codemirror/language-data": "^6.5.1", "@codemirror/state": "^6.5.2", "@codemirror/theme-one-dark": "^6.1.2", @@ -19,37 +20,22 @@ "core-js": "^3.37.1", "dot-prop": "^9.0.0", "litegraph.js": "^0.7.18", + "lodash": "^4.17.21", + "marked": "^15.0.12", "roboto-fontface": "*", "uuid": "^10.0.0", "vue": "^3.5", "vue-codemirror": "^6.1.1", - "vuetify": "^3.8", - "webfontloader": "^1.6.28", - "marked": "^15.0.12" + "vuetify": "^3.9", + "webfontloader": "^1.6.28" }, "devDependencies": { - "@babel/core": "^7", - "@babel/eslint-parser": "^7", - "@vue/cli-plugin-babel": "~5.0.8", + "@vitejs/plugin-vue": "^6.0.0", "eslint": "^9.0.0", "eslint-plugin-vue": "^10.0.0", "postcss": "^8.5.3", - "vue-cli-plugin-vuetify": "~2.5.8", - "webpack-plugin-vuetify": "^3.1" - }, - "eslintConfig": { - "root": true, - "env": { - "node": true - }, - "extends": [ - "plugin:vue/vue3-essential", - "eslint:recommended" - ], - "parserOptions": { - "parser": "@babel/eslint-parser" - }, - "rules": {} + "vite": "^7.0.4", + "vite-plugin-vuetify": "^2.1.1" }, "browserslist": [ "> 1%", diff --git a/talemate_frontend/src/components/AIAgent.vue b/talemate_frontend/src/components/AIAgent.vue index fec595e4..5897a08a 100644 --- a/talemate_frontend/src/components/AIAgent.vue +++ b/talemate_frontend/src/components/AIAgent.vue @@ -1,7 +1,8 @@ @@ -101,12 +124,19 @@ export default { } return types; + }, + regularMessages() { + return this.messages.filter(message => message.subtype !== 'function_call'); + }, + functionCallMessages() { + return this.messages.filter(message => message.subtype === 'function_call'); } }, data() { return { messages: [], max_messages: 20, + activeTab: 'messages', dirty: {}, intent: { intent: null, @@ -167,7 +197,7 @@ export default { } } }, - created() { + mounted() { this.registerMessageHandler(this.handleMessage); }, unmounted() { diff --git a/talemate_frontend/src/components/DirectorConsoleMessage.vue b/talemate_frontend/src/components/DirectorConsoleMessage.vue index 2595e0a1..f92c97b9 100644 --- a/talemate_frontend/src/components/DirectorConsoleMessage.vue +++ b/talemate_frontend/src/components/DirectorConsoleMessage.vue @@ -3,9 +3,27 @@
- mdi-brain - {{ message.message }} -
+ + {{ message.subtype === 'function_call' ? 'mdi-function-variant' : 'mdi-brain' }} + + + + + {{ message.message }} +
+
+ {{ key }}: {{ typeof value === 'object' ? JSON.stringify(value) : value }} +
+
+
+ + + {{ message.message }} +
{{ message.action }}
diff --git a/talemate_frontend/src/components/DirectorConsoleWidget.vue b/talemate_frontend/src/components/DirectorConsoleWidget.vue index 95e240ec..28c86403 100644 --- a/talemate_frontend/src/components/DirectorConsoleWidget.vue +++ b/talemate_frontend/src/components/DirectorConsoleWidget.vue @@ -1,8 +1,12 @@ - - diff --git a/talemate_frontend/src/components/NarratorMessage.vue b/talemate_frontend/src/components/NarratorMessage.vue index f2b7af81..34e2f5fd 100644 --- a/talemate_frontend/src/components/NarratorMessage.vue +++ b/talemate_frontend/src/components/NarratorMessage.vue @@ -56,9 +56,24 @@ mdi-source-fork Fork Scene + + + + mdi-account-voice + TTS + +
To edit the intro message open the mdi-scriptScene Editor + + + mdi-account-voice + TTS + +
@@ -93,6 +108,14 @@ export default { type: Boolean, default: false, }, + ttsAvailable: { + type: Boolean, + default: false, + }, + ttsBusy: { + type: Boolean, + default: false, + }, }, inject: [ 'requestDeleteMessage', @@ -105,6 +128,7 @@ export default { 'getMessageStyle', 'openWorldStateManager', 'reviseMessage', + 'generateTTS', ], computed: { parts() { diff --git a/talemate_frontend/src/components/SceneMessages.vue b/talemate_frontend/src/components/SceneMessages.vue index 6f8b42b6..0c34f367 100644 --- a/talemate_frontend/src/components/SceneMessages.vue +++ b/talemate_frontend/src/components/SceneMessages.vue @@ -6,11 +6,11 @@ @continue="(name, params) => { forkScene(params.message_id, name) }" />
-
+
- +
@@ -50,7 +50,7 @@
- +
@@ -70,13 +70,20 @@
- +
{{ message.text }}
+ +
+ + Stop audio + mdi-volume-high + +
@@ -109,6 +116,9 @@ export default { }, agentStatus: { type: Object, + }, + audioPlayedForMessageId: { + default: undefined, } }, components: { @@ -122,6 +132,7 @@ export default { ContextInvestigationMessage, SystemMessage, }, + emits: ['cancel-audio-queue'], data() { return { messages: [], @@ -137,7 +148,13 @@ export default { computed: { editorRevisionsEnabled() { return this.agentStatus && this.agentStatus.editor && this.agentStatus.editor.actions && this.agentStatus.editor.actions["revision"] && this.agentStatus.editor.actions["revision"].enabled; - } + }, + ttsAvailable() { + return this.agentStatus.tts?.available; + }, + ttsBusy() { + return this.agentStatus.tts?.busy || this.agentStatus.tts?.busy_bg; + }, }, inject: ['getWebsocket', 'registerMessageHandler', 'setWaitingForInput'], provide() { @@ -149,6 +166,7 @@ export default { getMessageColor: this.getMessageColor, getMessageStyle: this.getMessageStyle, reviseMessage: this.reviseMessage, + generateTTS: this.generateTTS, } }, methods: { @@ -247,6 +265,14 @@ export default { this.setWaitingForInput(false); }, + messageTypeAllowsAudio(type) { + return [ + 'narrator', + 'character', + 'context_investigation', + ].includes(type); + }, + messageTypeIsSceneMessage(type) { return ![ 'request_input', @@ -293,6 +319,14 @@ export default { })); }, + generateTTS(message_id) { + this.getWebsocket().send(JSON.stringify({ + type: 'tts', + action: 'generate_for_scene_message', + message_id: message_id, + })); + }, + handleMessage(data) { var i; @@ -333,11 +367,13 @@ export default { if (data.type == "message_edited") { // find the message by id and update the text# - for (i = 0; i < this.messages.length; i++) { if (this.messages[i].id == data.id) { if (this.messages[i].type == "character") { - this.messages[i].text = data.message.split(':')[1].trim(); + const parts = data.message.split(':'); + parts.shift(); + const text = parts.join(':'); + this.messages[i].text = text.trim(); } else { this.messages[i].text = data.message; } @@ -384,7 +420,8 @@ export default { id: data.id, type: data.type, character: data.character, - text: data.message, direction_mode: data.direction_mode, + text: data.message, + direction_mode: data.direction_mode, action: data.action } ); @@ -459,6 +496,10 @@ export default { overflow-y: auto; } +.message-wrapper { + position: relative; +} + .message { white-space: pre-wrap; } @@ -510,5 +551,9 @@ export default { gap: 10px; } -.message.request_input {} +.audio-played-indicator { + position: absolute; + top: 25px; + left: 5px; +} \ No newline at end of file diff --git a/talemate_frontend/src/components/SceneTools.vue b/talemate_frontend/src/components/SceneTools.vue index a0400737..5ded8e79 100644 --- a/talemate_frontend/src/components/SceneTools.vue +++ b/talemate_frontend/src/components/SceneTools.vue @@ -246,22 +246,8 @@ - - - - Save - - {{ option.title }} - {{ option.description }} - - - + +
@@ -279,7 +265,7 @@ import SceneToolsNarrator from './SceneToolsNarrator.vue'; import SceneToolsActor from './SceneToolsActor.vue'; import SceneToolsCreative from './SceneToolsCreative.vue'; import SceneToolsVisual from './SceneToolsVisual.vue'; - +import SceneToolsSave from './SceneToolsSave.vue'; export default { name: 'SceneTools', @@ -289,6 +275,7 @@ export default { SceneToolsActor, SceneToolsCreative, SceneToolsVisual, + SceneToolsSave, }, props: { appBusy: Boolean, @@ -349,12 +336,6 @@ export default { {"value": "toggleAutoSave", "title": "Auto Save", "icon": "mdi-content-save", "description": "Automatically save after each game-loop", "status": () => { return this.canAutoSave ? this.autoSave : "Manually save scene for auto-save to be available"; }}, {"value": "toggleAutoProgress", "title": "Auto Progress", "icon": "mdi-robot", "description": "AI automatically progresses after player turn.", "status": () => { return this.autoProgress }}, ], - - saveMenu: [ - {"value": "save_as", "title": "Save As", "icon": "mdi-content-save-all", "description": "Save the current scene as a new scene"}, - {"value": "save", "title": "Save", "icon": "mdi-content-save", "description": "Save the current scene"}, - ], - advanceTimeOptions: [ {"value" : "P10Y", "title": "10 years"}, {"value" : "P5Y", "title": "5 years"}, diff --git a/talemate_frontend/src/components/SceneToolsSave.vue b/talemate_frontend/src/components/SceneToolsSave.vue new file mode 100644 index 00000000..94a9c805 --- /dev/null +++ b/talemate_frontend/src/components/SceneToolsSave.vue @@ -0,0 +1,78 @@ + + + \ No newline at end of file diff --git a/talemate_frontend/src/components/TalemateApp.vue b/talemate_frontend/src/components/TalemateApp.vue index 898e5137..ab819880 100644 --- a/talemate_frontend/src/components/TalemateApp.vue +++ b/talemate_frontend/src/components/TalemateApp.vue @@ -39,8 +39,9 @@ - + + v{{ version }} @@ -67,14 +68,28 @@ + + - mdi-bug - mdi-cog - mdi-application-cog - mdi-application-cog + + + + + + + + + + + @@ -200,6 +215,8 @@ :appearance-config="appConfig ? appConfig.appearance : {}" :ux-locked="uxLocked" :agent-status="agentStatus" + :audio-played-for-message-id="audioPlayedForMessageId" + @cancel-audio-queue="onCancelAudioQueue" />
@@ -302,6 +319,7 @@ import AudioQueue from './AudioQueue.vue'; import StatusNotification from './StatusNotification.vue'; import RateLimitAlert from './RateLimitAlert.vue'; import VisualQueue from './VisualQueue.vue'; +import VoiceLibrary from './VoiceLibrary.vue'; import WorldStateManager from './WorldStateManager.vue'; import WorldStateManagerMenu from './WorldStateManagerMenu.vue'; import IntroView from './IntroView.vue'; @@ -337,6 +355,7 @@ export default { DirectorConsoleWidget, PackageManager, PackageManagerMenu, + VoiceLibrary, }, name: 'TalemateApp', data() { @@ -436,6 +455,7 @@ export default { lastAgentUpdate: null, lastClientUpdate: null, busy: false, + audioPlayedForMessageId: undefined, } }, watch:{ @@ -618,9 +638,9 @@ export default { this.connecting = true; let currentUrl = new URL(window.location.href); - let websocketUrl = process.env.VUE_APP_TALEMATE_BACKEND_WEBSOCKET_URL || `ws://${currentUrl.hostname}:5050/ws`; + let websocketUrl = import.meta.env.VITE_TALEMATE_BACKEND_WEBSOCKET_URL || `ws://${currentUrl.hostname}:5050/ws`; - console.log("urls", { websocketUrl, currentUrl }, {env : process.env}); + console.log("urls", { websocketUrl, currentUrl }, {env : import.meta.env}); this.websocket = new WebSocket(websocketUrl); console.log("Websocket connecting ...") @@ -638,8 +658,12 @@ export default { this.sceneActive = false; this.scene = {}; this.loading = false; - if(this.reconnect) - this.connect(); + if (this.reconnect) { + // Wait for the configured reconnectInterval before trying again to reduce rapid retry loops + setTimeout(() => { + this.connect(); + }, this.reconnectInterval); + } }; this.websocket.onerror = (error) => { console.log('WebSocket error', error); @@ -1086,10 +1110,48 @@ export default { this.websocket.send(JSON.stringify({ type: 'request_app_config' })); }, saveClients(clients) { - this.websocket.send(JSON.stringify({ type: 'configure_clients', clients: clients })); + console.log("saveClients", clients) + + const saveData = {} + + for(let client of clients) { + saveData[client.name] = { + ...client, + } + } + this.websocket.send(JSON.stringify({ type: 'configure_clients', clients: saveData })); }, saveAgents(agents) { - this.websocket.send(JSON.stringify({ type: 'configure_agents', agents: agents })); + const saveData = {} + + for(let agent of agents) { + console.log("agent", agent) + const requiresLLM = agent.data?.requires_llm_client || false; + + let client; + + if(requiresLLM) { + + if(agent.client?.client) { + client = agent.client?.client?.value; + } else { + client = agent.client || null; + } + + } else { + client = null; + } + + saveData[agent.name] = { + enabled: agent.enabled, + actions: agent.actions, + client: client, + } + } + + console.log("saveAgents",{ saveData, agents }) + + this.websocket.send(JSON.stringify({ type: 'configure_agents', agents: saveData })); }, requestSceneAssets(asset_ids) { this.websocket.send(JSON.stringify({ type: 'request_scene_assets', asset_ids: asset_ids })); @@ -1311,6 +1373,17 @@ export default { this.websocket.send(JSON.stringify({ type: 'interact', text: "!setenv_scene" })); }, + onMessageAudioPlayed(messageId) { + this.audioPlayedForMessageId = messageId; + }, + + onCancelAudioQueue() { + this.audioPlayedForMessageId = undefined; + if(this.$refs.audioQueue) { + this.$refs.audioQueue.stopAndClear(); + } + }, + } } diff --git a/talemate_frontend/src/components/VoiceLibrary.vue b/talemate_frontend/src/components/VoiceLibrary.vue new file mode 100644 index 00000000..a107c7f6 --- /dev/null +++ b/talemate_frontend/src/components/VoiceLibrary.vue @@ -0,0 +1,881 @@ + + + + + \ No newline at end of file diff --git a/talemate_frontend/src/components/VoiceLibraryCharacterManager.vue b/talemate_frontend/src/components/VoiceLibraryCharacterManager.vue new file mode 100644 index 00000000..a96f2de1 --- /dev/null +++ b/talemate_frontend/src/components/VoiceLibraryCharacterManager.vue @@ -0,0 +1,315 @@ + + + + + \ No newline at end of file diff --git a/talemate_frontend/src/components/VoiceMixer.vue b/talemate_frontend/src/components/VoiceMixer.vue new file mode 100644 index 00000000..69270a4b --- /dev/null +++ b/talemate_frontend/src/components/VoiceMixer.vue @@ -0,0 +1,393 @@ + + + + + \ No newline at end of file diff --git a/talemate_frontend/src/components/VoiceSelect.vue b/talemate_frontend/src/components/VoiceSelect.vue new file mode 100644 index 00000000..f072aa2f --- /dev/null +++ b/talemate_frontend/src/components/VoiceSelect.vue @@ -0,0 +1,133 @@ + + + + + \ No newline at end of file diff --git a/talemate_frontend/src/components/WhatsNew.vue b/talemate_frontend/src/components/WhatsNew.vue index aa95b674..e2f277b4 100644 --- a/talemate_frontend/src/components/WhatsNew.vue +++ b/talemate_frontend/src/components/WhatsNew.vue @@ -25,13 +25,13 @@ - + {{ feature.title }} - - + + Dialogue Instructions - + Dialogue Examples + + Voice + @@ -65,19 +68,59 @@ /> - - - - -
- {{ example }} -
-
-
+ +
+ + +
+
+ + Remove this example + mdi-close-box-outline + +
+
+
+
+
+
+ + + + Test Voice + + + + Select a voice for {{ character.name }}. Only voices from ready TTS APIs are listed. +
@@ -89,12 +132,15 @@ import ContextualGenerate from './ContextualGenerate.vue'; import SpiceAppliedNotification from './SpiceAppliedNotification.vue'; +import VoiceSelect from './VoiceSelect.vue'; +import { parseSceneText } from '../utils/sceneMessageRenderer.js'; export default { name: 'WorldStateManagerCharacterActor', components: { ContextualGenerate, SpiceAppliedNotification, + VoiceSelect, }, data() { return { @@ -103,6 +149,9 @@ export default { dialogueExample: "", dialogueInstructions: null, dialogueInstructionsDirty: false, + voiceId: null, + voiceDirty: false, + testingVoice: false, updateCharacterActorTimeout: null, } }, @@ -124,9 +173,11 @@ export default { handler() { this.dialogueInstructions = this.character.actor.dialogue_instructions; this.dialogueExamples = this.character.actor.dialogue_examples; + this.voiceId = this.character.voice ? this.character.voice.id : null; }, - deep: true - } + deep: true, + immediate: true, + }, }, props: { character: Object, @@ -149,11 +200,19 @@ export default { type: "world_state_manager", action: "update_character_actor", name: this.character.name, - dialogue_instructions: this.dialogueInstructions, + dialogue_instructions: this.dialogueInstructions || "", dialogue_examples: this.dialogueExamples, })); }, + handleDialogeExampleEnter(event) { + if(!event.shiftKey) { + this.dialogueExamples.push(this.dialogueExample); + this.dialogueExample = ''; + this.updateCharacterActor(); + } + }, + setCharacterDialogueInstructions(instructions) { this.dialogueInstructions = instructions; this.dialogueInstructionsDirty = true; @@ -172,12 +231,63 @@ export default { if(data.type === 'world_state_manager') { if(data.action === 'character_actor_updated') { this.dialogueInstructionsDirty = false; + } else if (data.action === 'character_voice_updated') { + this.voiceDirty = false; } else if (data.action === 'character_dialogue_instructions_generated') { this.dialogueInstructions = data.data.instructions; this.dialogueInstructionsBusy = false; } + } else if (data.type === 'tts') { + if (data.action === 'operation_done' || data.action === 'operation_failed') { + this.testingVoice = false; + } } }, + + updateCharacterVoice() { + if(!this.voiceDirty) return; + + this.getWebsocket().send(JSON.stringify({ + type: 'world_state_manager', + action: 'update_character_voice', + name: this.character.name, + voice_id: this.voiceId, + })); + }, + + testCharacterVoice() { + if (!this.voiceId || this.testingVoice) return; + + // Extract provider and provider_id from voiceId (format: "provider:provider_id") + const [provider, ...providerIdParts] = this.voiceId.split(':'); + const provider_id = providerIdParts.join(':'); + + // Get a random dialogue example, or use default text + let testText = "This is a test of the selected voice."; + if (this.dialogueExamples && this.dialogueExamples.length > 0) { + const randomIndex = Math.floor(Math.random() * this.dialogueExamples.length); + testText = this.dialogueExamples[randomIndex]; + // Strip character name prefix if present + if (testText.startsWith(this.character.name + ':')) { + testText = testText.substring(this.character.name.length + 1).trim(); + } + } + + this.testingVoice = true; + this.getWebsocket().send(JSON.stringify({ + type: 'tts', + action: 'test', + provider: provider, + provider_id: provider_id, + provider_model: this.character.voice.provider_model, + parameters: this.character.voice.parameters, + text: testText, + })); + }, + + renderSceneText(text) { + return parseSceneText(text); + }, }, created() { this.registerMessageHandler(this.handleMessage); @@ -185,6 +295,7 @@ export default { mounted() { this.dialogueInstructions = this.character.actor.dialogue_instructions; this.dialogueExamples = this.character.actor.dialogue_examples; + this.voiceId = this.character.voice ? this.character.voice.id : null; }, } diff --git a/talemate_frontend/src/components/WorldStateManagerCharacterCreator.vue b/talemate_frontend/src/components/WorldStateManagerCharacterCreator.vue index 9c1a8fad..20b93dfb 100644 --- a/talemate_frontend/src/components/WorldStateManagerCharacterCreator.vue +++ b/talemate_frontend/src/components/WorldStateManagerCharacterCreator.vue @@ -152,6 +152,21 @@ export default { this.character = {}; this.$emit('cancelled'); }, + + reset() { + this.character = { + generation_context: { + enabled: true, + instructions: "", + generateAttributes: true, + }, + description: "", + name: "", + templates: [], + is_player: false, + } + }, + sendAutocompleteRequest() { this.descriptionBusy = true; this.autocompleteRequest({ @@ -220,6 +235,7 @@ export default { if(this.character.created) { this.character.created(message.data); } + this.reset(); } // Handle director responses (for AI generation) else if (message.type === 'director' && message.action === 'character_persisted') { @@ -228,6 +244,7 @@ export default { if(this.character.created) { this.character.created(message.character); } + this.reset(); } else if ((message.type === 'director' || message.type === 'world_state_manager') && message.action === 'operation_done') { this.busy = false; diff --git a/talemate_frontend/src/components/WorldStateManagerSceneExport.vue b/talemate_frontend/src/components/WorldStateManagerSceneExport.vue index fb62c4f5..d619dee3 100644 --- a/talemate_frontend/src/components/WorldStateManagerSceneExport.vue +++ b/talemate_frontend/src/components/WorldStateManagerSceneExport.vue @@ -28,6 +28,43 @@ > + + + + Include in Export + + + + + + + + + + + + + + + + + @@ -54,12 +91,17 @@ export default { data() { return { formats: [ - { value: 'talemate', title: 'Talemate Scene' }, + { value: 'talemate', title: 'Talemate Scene (JSON only)' }, + { value: 'talemate_complete', title: 'Complete Scene Package (ZIP)' }, ], - format: 'talemate', + format: 'talemate_complete', resetProgress: true, exportName: '', formIsValid: false, + includeAssets: true, + includeNodes: true, + includeInfo: true, + includeTemplates: true, rules: { required: value => !!value || 'Required.' } @@ -74,13 +116,23 @@ export default { return; } - this.getWebsocket().send(JSON.stringify({ + const exportOptions = { type: 'world_state_manager', action: 'export_scene', format: this.format, reset_progress: this.resetProgress, name: this.exportName, - })); + }; + + // Add complete export options if selected + if (this.format === 'talemate_complete') { + exportOptions.include_assets = this.includeAssets; + exportOptions.include_nodes = this.includeNodes; + exportOptions.include_info = this.includeInfo; + exportOptions.include_templates = this.includeTemplates; + } + + this.getWebsocket().send(JSON.stringify(exportOptions)); }, handleMessage(message) { if (message.type !== 'world_state_manager') { @@ -89,13 +141,24 @@ export default { if (message.action === 'scene_exported') { const scene_b64 = message.data; + const format = message.format || 'talemate'; + const fileExtension = message.file_extension || 'json'; + // prepare data url for download - const data = `data:application/octet-stream;base64,${scene_b64}`; + const mimeType = fileExtension === 'zip' ? 'application/zip' : 'application/json'; + const data = `data:${mimeType};base64,${scene_b64}`; + // trigger download const a = document.createElement('a'); a.href = data; - a.download = `${this.exportName}.json`; + a.download = `${this.exportName}.${fileExtension}`; a.click(); + + // Show success message + this.$emit('show-message', { + message: `Scene exported successfully as ${this.exportName}.${fileExtension}`, + type: 'success' + }); } } }, diff --git a/talemate_frontend/src/components/WorldStateManagerWorldEntries.vue b/talemate_frontend/src/components/WorldStateManagerWorldEntries.vue index d72b27b4..e0319765 100644 --- a/talemate_frontend/src/components/WorldStateManagerWorldEntries.vue +++ b/talemate_frontend/src/components/WorldStateManagerWorldEntries.vue @@ -1,11 +1,13 @@