commit de51967b49f5a232ddbc726c90dd72f900a5b189 Author: Xiaohan-Tian <157918347+Xiaohan-Tian@users.noreply.github.com> Date: Mon Aug 11 18:37:21 2025 -0700 initial public release. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a4f8296 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +res/ +dist +dist-ssr +*.local +CLAUDE.md + +# Editor directories and files +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +*.psd diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..326fc67 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,19 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:5173", + "webRoot": "${workspaceFolder}", + "sourceMaps": true, + "sourceMapPathOverrides": { + "/@fs/*": "${webRoot}/*", + "webpack:///./~/*": "${webRoot}/node_modules/*", + "webpack://?:*/*": "${webRoot}/*" + }, + "preLaunchTask": "npm: dev" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..cc781ad --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "dev", + "label": "npm: dev", + "detail": "vite", + "isBackground": true, + "options": { + "shell": { + "executable": "/bin/zsh", + "args": ["-c", "source ~/.nvm/nvm.sh && nvm use 20 && npm run dev"] + } + }, + "problemMatcher": { + "pattern": { + "regexp": "^.*$" + }, + "background": { + "activeOnStart": true, + "beginsPattern": ".*", + "endsPattern": "ready in .*ms" + } + } + } + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4c514fd --- /dev/null +++ b/LICENSE @@ -0,0 +1,135 @@ +# License + +Copyright 2025 Xiaohan Tian + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this project except in compliance with the License. +You may obtain a copy of the License at: + + http://www.apache.org/licenses/LICENSE-2.0 + +## Additional Terms + +1. **No Patent Applications** + You may not use this software or any included assets, in whole or in part, to file or assist in filing any patent application, whether directly or indirectly, in any jurisdiction. + +2. **Attribution Requirement** + If you use this project in any public or commercial product, you must provide the following credit in a prominent location (such as an "About" screen or documentation): + + > Powered by **K.G.Studio** + > © 2025 Xiaohan Tian + +## Standard Terms + +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an **"AS IS" BASIS**, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +--- + +## Third-Party Components + +This project includes resources from the following third-party projects. +Their original license notices **must be retained** in all copies or substantial portions of this project. + +--- + +### 1. midi-js-soundfonts + +``` +Copyright (C) 2012 Benjamin Gleitzman (gleitz@mit.edu) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +``` + +--- + +### 2. FluidR3_GM SoundFont + +``` +Fluid (R3) SoundFont + +Copyright (c) 2000-2002, 2008, 2013 Frank Wen + +I hereby release Fluid under the MIT license, as described in COPYING. + +Regardless the implications or stipulations of the MIT license, the wishes +and intent of the author are as follows: + +1. This soundfont is provided for free, for unlimited use, and can be used + interchangably with other soundfonts for personal or commercial music + composition. + +2. In regards to bundling Fluid with software or hardware for commercial + purposes (freeware with paid features, or paid completely), please + follow these guidelines so that Fluid will not be construed as a commodity. + + -Your design must have openness for anyone to be able to load whatever + soundfont they want to use, and have full access to all the instruments + in the soundfont. + + -You cannot lock access to Fluid by having people have to pay in order + to get access to more instrument banks or instrument banks associated + with specific tracks in the sequencer. + + -If you are confused, please contact me. + +3. If you use part of Fluid to make a new bank for public domain, document it + and credit me. + +4. Realistically, I cannot police or enforce what actually happens to Fluid, + although I believe there are honorable people who have respected my wishes + to keep the spirit of what I wanted the work to be, and thank all who have + supported and used Fluid in their musical endeavors. + + +This package, of course, is the original Release 3 of Fluid. + +Fluid was constructed in part from samples found in the public domain that I +edited/cleaned/remixed/programmed and largely from recordings of my own and +in conjunction with the people below who helped along the way: + +Suren M. Seron +Scott Hanan +Steve Aupperle +Chris Gillman +Alex Taubr +Chris Prola +Andrew Klenk +Winfried Hubbe +Dylan +Tim +Gort +Uros Katic +Ethan Winer (http://www.ethanwiner.com) + +I'd also like to thank Alexander Ryan, Dan Reagan, and Thomas Hammer for +hosting the project for so many years. Without them, distribution would have +been impossible. + +And also Tobe Smithe, for bringing this to more people through Ubuntu. + +It's obviously been a few years since the project, but its nice to see that +people are still enjoying my work and getting good use out of it. As always, +I'd like to hear some work done with Fluid so email me, or just email me to +say hello and tell me what is going on in the computer musician world. +Who knows, maybe I'll kick start this project again? ;) +``` + +--- + +### 3. System Prompt Structure (based on Cline) + +Portions of the system prompt structure are derived from the Cline project, +licensed under the Apache License, Version 2.0. + +Original project: [Cline](https://github.com/cline/cline) + +Modifications: Substantial textual and functional changes have been made; only the general content structure remains. + +--- + +Apache License Version 2.0, January 2004: http://www.apache.org/licenses/LICENSE-2.0 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed43e39 --- /dev/null +++ b/README.md @@ -0,0 +1,154 @@ +
+ K.G.Studio Logo +
+ +## K.G.Studio — Browser-based DAW (Digital Audio Workstation) + +K.G.Studio is a lightweight, modern DAW that runs entirely in the browser with **K.G.Studio Musician Assistant** at its core. It features realistic instrument playback via Tone.js samplers, a piano‑roll editor, track and region management with full undo/redo, project persistence to IndexedDB, a configurable settings panel, and an integrated AI assistant with tool execution. + +**K.G.Studio Musician Assistant** is an AI assistance agent for harmony, arrangement, and note editing — but not full auto‑composition. + +### Highlights +- **K.G.Studio Musician Assistant**: Chat with the LLM‑powered K.G.Studio Musician Assistant AI Agent; it can automatically execute tools to make music edits. +- **Multiple LLM providers**: OpenAI, Claude (via OpenRouter), Gemini (via OpenRouter), or OpenAI‑compatible (e.g., Ollama, OpenRouter). +- **Track & Region editing**: Add/reorder tracks, create/move/resize regions, copy/paste regions, and more. +- **Piano roll**: Create and edit notes with snapping/quantization support. +- **Real instruments**: Tone.js‑based Sampler with high‑quality FluidR3 soundfonts. +- **Undo/Redo everywhere**: Command pattern for tracks, regions, notes, and project properties +- **Persistence with privacy**: Save/load projects and configuration in your browser’s IndexedDB (on your device). +- **Export/Import**: Export your project as a MIDI file, or import a MIDI file into your project. +- **Settings**: LLM provider, AI agent custom instructions, app behavior, and more. + +For a deeper technical overview, see `res/overview.md`. + +## Getting Started + +### Prerequisites +- Node.js 20.19.3+ + +### Install & Run +```bash +git clone https://github.com/Xiaohan-Tian/KGSP {your-local-path} +cd {your-local-path} + +npm install +npm run dev +``` + +## Configuration + +K.G.Studio loads defaults from `./public/config.json` (with an internal fallback) and persists user edits in the browser via `ConfigManager` + IndexedDB. IndexedDB is your browser’s own local database stored on your device; it does not leave your machine and is cleared if you clear this site’s data. Modify settings via the in‑app Settings panel. + +- **General** + - LLM provider: OpenAI, or OpenAI‑compatible + - API keys and models for the selected provider + - OpenAI‑compatible base URL (for self‑hosted gateways) + - Soundfont base URL (CDN for instrument samples) +- **Behavior** + - Chatbox default open on startup +- **Templates** + - Custom instructions used by the AI assistant + +### Connectivity & Privacy + +- K.G.Studio is fully client‑side. No first‑party servers are required to run the app. +- All projects, configuration, and UI state are stored in your browser’s IndexedDB (on your device). +- Network access is only used for: + - Downloading instrument sound samples from the configured soundfont CDN + - Communicating with the LLM provider you select (e.g., OpenAI or OpenAI‑compatible services) +- Outside of the two cases above, the app functions locally. If you block those endpoints, the app still loads; instrument playback and AI features will not function until network access is restored. +- For security, when running from a non‑local host we do not persist your API key in IndexedDB (to reduce XSS risk). You’ll be prompted to enter it each time you start K.G.Studio. + +## Using the App + +1. Start the app and a default project will load. A default “Melody” MIDI track is ensured on load. +2. Add tracks, rename and reorder them. Click or drag in the track grid to create and arrange regions. +3. Add a region by double‑clicking or holding Ctrl/Cmd and clicking. Drag the edges to resize a region; drag the center to move it. Use Ctrl/Cmd+C and Ctrl/Cmd+V to copy/paste regions. +4. Open a MIDI region to edit notes in the Piano Roll window (click the pencil icon on the region’s top‑left corner, or click the Piano icon on the toolbar). +5. Double‑click or hold Ctrl/Cmd and click to create a note. Drag the edges to change its length, or drag the center to move it. Use box selection or hold Shift to multi‑select. +6. Quantize note length/position using the Quantize dropdowns at the top‑right of the piano roll. +7. Change instruments via the Instrument Selection panel; samples are downloaded on demand. +8. Save projects to your browser’s IndexedDB and load by name from the toolbar. Export your project as a MIDI file, or import a MIDI file into your project. + +## Keyboard Shortcuts (defaults) + +- Main + - Play/Pause: Space + - Undo / Redo: Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z + - Copy / Cut / Paste: Ctrl/Cmd+C / Ctrl/Cmd+X / Ctrl/Cmd+V + - Save: Ctrl/Cmd+S + - Hold to create region: Ctrl/Cmd +- Piano Roll + - Tools: Select (Q), Pencil (W) + - Hold to create note: Ctrl/Cmd + - Snap: 1(None), 2(1/4), 3(1/8), 4(1/16) + - Quantize Position: 5(1/4), 6(1/8), 7(1/16) + - Quantize Length: 8(1/4), 9(1/8), 0(1/16) + +## AI Assistant + +### Quick Start + +- First, select an LLM provider in **Settings ⚙️ → General → LLM Provider**. If you are using OpenAI (e.g., GPT‑4o), get your API key from [**OpenAI**](https://platform.openai.com/account/api-keys) and paste it in **OpenAI → Key**. +- You can find the K.G.Studio Musician Assistant chatbox on the right. If you don't see it, you can click the Chat 🗨️ button on the toolbar. +- Type your message in the chatbox; press Enter to send, Shift+Enter to insert a new line. +- The agent will automatically process your request and invoke tools to make modifications scoped to the selected region. It may take one or more turns to complete a task. +- Note that the AI could make mistakes, so you should always check the result and make adjustments if necessary. You can also use undo/redo to revert the changes. +- Click the "+" button or `/clear` command to clear the chat history. + +### Configuring Your LLM Provider + +Navigate to **Settings ⚙️ → General → LLM Provider**. Depending on your chosen provider, you will need to supply the appropriate API Key and, if applicable, a custom base URL (for non-official OpenAI-compatible services such as Ollama, OpenRouter, etc.). + +Note: due to CORS limitations with some providers, Google Gemini and Anthropic Claude are currently supported via OpenRouter only. + +### Using OpenAI models + +1. Obtain an OpenAI API Key from [**OpenAI**](https://platform.openai.com/account/api-keys). You may need to create an account and add a payment method to generate an API Key. +2. In **Settings ⚙️ → General → LLM Provider**, select **OpenAI** as your provider. +3. Enter your API Key in **OpenAI → Key**. +4. Select your preferred model from the **OpenAI → Model** dropdown. For optimal performance, we recommend `gpt‑4o`. (`gpt‑5` is still being evaluated.) +5. Optionally, choose whether to enable Flex Mode in **OpenAI → Flex Mode**. Flex Mode offers discounted pricing, but may result in slower response times or server-side errors. + +### Using OpenRouter + +OpenRouter is a platform that provides unified access to a wide range of language models—including free options—from various providers. This makes it easy to experiment and find the model that best suits your needs. + +1. Obtain an API Key from [**OpenRouter**](https://openrouter.ai/keys). Registration is required; for paid models, a payment method may also be necessary. +2. In **Settings ⚙️ → General → LLM Provider**, select **OpenAI Compatible** as your provider. +3. Enter your API Key in **OpenAI Compatible Server → Key**. +4. Browse available models on the [**OpenRouter Models Page**](https://openrouter.ai/models). Use the "Prompt Pricing" filter to identify free models. + **Note:** Each model provider may have different data retention and privacy policies. Please review these policies before use. +5. Enter your chosen model name in **OpenAI Compatible Server → Model**. Recommended model series include: + - `Google: gemini-2.5-pro` + - `Anthropic: claude-4-sonnet` + - `DeepSeek: deepseek-r1` (free: `deepseek/deepseek-r1-0528:free`) + - `DeepSeek: deepseek-v3` (free: `deepseek/deepseek-chat-v3-0324:free`) + - `Qwen: qwen3-235b-a22b` (free: `qwen/qwen3-235b-a22b:free`) +6. Input the base URL `https://openrouter.ai/api/v1/chat/completions` **OpenAI Compatible Server → Base URL**. + +### About the agent and LLM providers + +Currently, based on our evaluation, OpenAI’s open‑source models (`gpt‑oss‑20b` and `gpt‑oss‑120b`) are not yet compatible with the current agent implementation; support is planned. + +For security, when using K.G.Studio from a non‑local host, API keys are not persisted in IndexedDB; you will need to input your API key each time you start K.G.Studio. + +K.G.Studio does not provide or host any of the models listed above, nor is it affiliated with any model provider. All data is stored locally on your device; K.G.Studio does not collect or transmit your data. You are solely responsible for any data you provide to third‑party model providers. + +## Upcoming Features + +- [ ] More instruments +- [ ] Support track control automations (e.g. sustain, volume, pan, etc.) +- [ ] Support MIDI control events (e.g. CC, pitch bend, etc.) +- [ ] Support WAV audio tracks +- [ ] MCP Support +- [ ] Add support for OpenAI's open source models (`gpt-oss-20b` and `gpt-oss-120b`) +- [ ] Automatically compact conversations when the context window runs low on space + +## License + +Licensed under the Apache License, Version 2.0, with additional terms (see `LICENSE`): +- No patent applications using this software or assets +- Attribution required when used in public/commercial products (“Powered by K.G.Studio”) + +Third‑party notices (e.g., FluidR3_GM SoundFont, midi‑js‑soundfonts, and prompt structure notes) are included in `LICENSE`. diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md new file mode 100644 index 0000000..159b2bc --- /dev/null +++ b/docs/USER_GUIDE.md @@ -0,0 +1,179 @@ +# K.G.Studio User Guide + +A lightweight, browser‑based DAW with an AI Agent "K.G.Studio Musician Assistant". This guide walks you through setup, the interface, and everyday workflows. + +## 1. Introduction +- K.G.Studio runs entirely in your browser. It provides track and region editing, a piano‑roll editor, realistic instrument playback (Tone.js samplers with FluidR3 soundfonts), robust undo/redo, and project persistence. +- K.G.Studio Musician Assistant can respond to natural language prompts and execute edit tools on your behalf. + +## 2. System Requirements +- Tested on Chrome, Firefox, and Safari. It should work on other modern browsers, but those are not officially tested yet. +- macOS, Windows, or Linux. +- Network access is only needed for downloading instrument sound samples and contacting your selected LLM provider. + +## 3. Quick Start +- **Use the hosted app: [K.G.Studio (kgaudiolab.github.io/kgstudio)](https://kgaudiolab.github.io/kgstudio)** +- Or clone and run locally: + ```bash + # Make sure you have Node.js >= 20.19.3 installed + # Clone the repository + git clone https://github.com/KGAudioLab/KGStudio {your-local-path} + cd {your-local-path} + + # Install dependencies + npm install + + # Run the development server + npm run dev + ``` +- First run: + - A welcome message will be shown on the K.G.Studio Musician Assistant chatbox, please follow the instructions to configure the LLM provider. +- Your data is stored in your browser’s IndexedDB (on your device). + +## 4. Data, Privacy, and Connectivity +- IndexedDB is your browser’s local database for this site; it never leaves your machine and is cleared if you clear the site’s data. +- Per‑origin storage: IndexedDB is not shared across different hosts/origins, across different browsers, or separate browser profiles. To move or share your work, use Export (KGStudio JSON) and Import on the destination. +- K.G.Studio is fully client‑side. It connects to the network only to: + - Download instrument soundfonts from the configured CDN + - Communicate with your chosen LLM provider (OpenAI or OpenAI‑compatible) +- API keys are not persisted when running from a non‑local host (to reduce XSS risk). You’ll be prompted to re‑enter them on each start in that scenario. +- Important: While K.G.Studio does not collect your data, different LLM providers may have different data‑retention policies. Review the policy of the provider you select before use. + +## 5. User Interface Tour +### Toolbar +- Left + - Logo + - Project name (click to rename) +- Center (exact order) + - New, Load, Save, Export (dropdown), Import + - Undo, Redo + - Pointer tool, Pencil tool + - Copy, Paste, Delete + - Back to beginning, Play/Pause + - Piano button (open piano roll for the active/selected region) +- Right + - Transport readouts: current time | BPM | time signature | key signature + - Click time to set max bars, BPM to change BPM, time signature to change signature; key signature opens a dropdown. + - Settings (gear) + - Chat (speech bubble) — toggles the AI chatbox on the right + +### Track Area +- Track info column + - Solo (S), Mute (M), volume, instrument button (piano icon) + - Settings button (to the right of the instrument button) with a Delete Track option +- Track grid + - Regions display as blocks with a header; the small pencil on the top‑left of a region opens its Piano Roll. + +### Instrument Selection Panel +- Appears automatically on load/first track creation, or when clicking a track’s instrument button (to the right of “M”). +- Choose instrument groups and individual instruments; a large preview is shown. +- Stays open until you close it (X) or click the same track’s instrument button again. + +### Chatbox (AI Assistant) +- Docked on the right; toggle from the toolbar. +- Press Enter to send; Shift+Enter for a new line. + +### Status Bar and Loading Overlay +- Status messages appear at the bottom. +- A global loading overlay shows while soundfonts are downloading. + +### Piano Roll Window +- Header: close (X) on the left; title (click to rename); tools (Select, Pencil); menus on the right: NO SNAP, Qua. Pos. (quantize start), Qua. Len. (quantize length). +- ESC closes the piano roll. + +## 6. Projects +- New, Save (to IndexedDB), Load (from IndexedDB by name). +- Export: KGStudio JSON, MIDI. +- Import: KGStudio JSON (replaces current project state), MIDI (appends tracks/regions into the current project). +- Legacy compatibility: projects are upgraded to the latest structure on load. + +## 7. Tracks +- Add, rename, and reorder tracks. +- Change instrument (instrument button in the track info row). Real instruments are powered by Tone.Sampler and FluidR3 soundfonts. +- Solo/Mute/Volume controls for quick mixing. +- Track settings (button to the right of the instrument) provides Delete Track. + +## 8. Regions +- Create regions + - Pointer tool: double‑click; or hold Ctrl/Cmd and click + - Pencil tool: single‑click to create +- Move/resize by dragging the region or its edges; copy/paste with toolbar buttons or shortcuts. +- Delete selected regions from the toolbar delete button. +- Open Piano Roll via the region’s small pencil on the top‑left. + +## 9. Piano Roll (MIDI Editing) +- Tools: Select vs Pencil. +- Create notes: double‑click or Ctrl/Cmd+click (Select); single‑click (Pencil). +- Move/resize notes by dragging; box‑select or Shift‑click for multi‑selection. +- NO SNAP menu controls snapping for create/move/resize. +- Quantize using Qua. Pos. (start) and Qua. Len. (length) menus. +- Close with X or ESC. + +## 10. Playback and Transport +- Back to beginning; Play/Pause. +- Set playhead by clicking bar numbers in the main grid; in Piano Roll, click the header timeline (respects snapping). +- Edit time/BPM/time signature/key signature via the toolbar readouts. + +## 11. Undo/Redo and Clipboard +- Undo/Redo available for tracks, regions, notes, and project properties. +- Copy/Paste works for regions in the main grid and notes in the piano roll. +- Use toolbar buttons or keyboard shortcuts. + +## 12. Instruments and Soundfonts +- Instruments are organized by groups (e.g., Piano & Keyboards, Strings, Brass, Woodwind, Percussion Kit, Synthesizer). +- Playback uses high‑quality FluidR3 soundfonts via Tone.Sampler. +- If loading stalls, the overlay will time out; refresh to retry downloading. + +## 13. Settings +- General: LLM provider (OpenAI or OpenAI‑compatible), API key, model, soundfont base URL. +- Behavior: chatbox default open at startup. +- Templates: custom instructions for the AI. +- Settings persist in IndexedDB (local); API keys are not persisted on non‑local hosts. + +## 14. AI Agent "K.G.Studio Musician Assistant" +- Open the chat (toolbar). Describe goals in natural language (e.g., “Can you help me write a 4‑bar chord progression for the melody?”). +- The agent executes tools to edit your project. Actions are typically scoped to the selected region. +- Slash‑commands: + - `/clear` — clear chat history + - `/welcome` — show the welcome message +- Providers: OpenAI or OpenAI‑compatible (e.g., OpenRouter). Due to CORS, some providers are supported via OpenRouter. + - Reminder: LLM providers differ in data retention and usage policies. Check your provider’s policy and configure keys/models accordingly. + +## 15. Keyboard Shortcuts (defaults) +- Global + - Play/Pause: Space + - Undo / Redo: Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z + - Copy / Cut / Paste: Ctrl/Cmd+C / Ctrl/Cmd+X / Ctrl/Cmd+V + - Save: Ctrl/Cmd+S + - Hold to create region: Ctrl/Cmd +- Piano Roll + - Tools: Select (Q), Pencil (W) + - Hold to create note: Ctrl/Cmd + - Snap: 1(None), 2(1/4), 3(1/8), 4(1/16) + - Quantize Position: 5(1/4), 6(1/8), 7(1/16) + - Quantize Length: 8(1/4), 9(1/8), 0(1/16) + +## 16. Troubleshooting +- No sound: ensure soundfonts can download; check network; try refreshing. +- Chat/LLM errors: verify provider, base URL (if compatible), API key, and model name; check CORS if using third‑party gateways. +- MIDI import: only valid `.mid/.midi` files; malformed files will show an error. +- Performance: close unused panels; reduce concurrent soundfont loads. + +## 17. FAQ +- Where are my projects saved? In your browser’s IndexedDB on your device. +- Can I use the app offline? Yes, core editing works offline; instruments and AI need network when first used. +- Does the app send my projects to a server? No. Only your LLM requests go to your chosen provider. + +## 18. Glossary +- Project, Track, Region, Note, Snapping, Quantize, General MIDI (GM), IndexedDB, Soundfont, LLM. + +## 19. Known Limitations +- Limited note editing (no velocity editing yet) +- No region duplication shortcut +- Export UI present; audio export not implemented +- No audio recording yet; limited effects processing +- No project browser UI (load by name) + +## 20. Credits and Licenses +- Licensed under Apache 2.0 with additional terms (see `LICENSE`). +- Third‑party attributions: FluidR3_GM soundfont, midi‑js‑soundfonts, and prompt structure notes. diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..fd7545f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,27 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { globalIgnores } from 'eslint/config' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + rules: { + '@typescript-eslint/no-unused-vars': 'warn', // Downgrade from error to warning + 'no-unused-vars': 'warn' // Also set the base rule to warn + } + }, +]) diff --git a/index.html b/index.html new file mode 100644 index 0000000..0773718 --- /dev/null +++ b/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + K.G.Studio + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..75bc4d1 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,5231 @@ +{ + "name": "kgsp", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "kgsp", + "version": "0.0.0", + "dependencies": { + "class-transformer": "^0.5.1", + "idb": "^8.0.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", + "react-syntax-highlighter": "^15.6.1", + "reflect-metadata": "^0.2.2", + "remark-gfm": "^4.0.1", + "tone": "^15.1.22", + "zustand": "^5.0.6" + }, + "devDependencies": { + "@eslint/js": "^9.29.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@types/react-syntax-highlighter": "^15.5.13", + "@vitejs/plugin-react": "^4.5.2", + "eslint": "^9.29.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.2.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.34.1", + "vite": "^7.0.0" + } + }, + "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, + "license": "Apache-2.0", + "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, + "license": "MIT", + "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.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "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/generator": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "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, + "license": "MIT", + "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-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "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-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, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "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-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "license": "MIT", + "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, + "license": "MIT", + "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.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", + "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", + "dev": true, + "license": "Apache-2.0", + "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==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "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, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.1.tgz", + "integrity": "sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "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, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "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, + "license": "MIT", + "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, + "license": "MIT", + "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, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.2.tgz", + "integrity": "sha512-g0dF8P1e2QYPOj1gu7s/3LVP6kze9A7m6x0BZ9iTdXK8N5c2V7cpBKHV3/9A4Zd8xxavdhK0t4PnqjkqVmUc9Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.2.tgz", + "integrity": "sha512-Yt5MKrOosSbSaAK5Y4J+vSiID57sOvpBNBR6K7xAaQvk3MkcNVV0f9fE20T+41WYN8hDn6SGFlFrKudtx4EoxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.2.tgz", + "integrity": "sha512-EsnFot9ZieM35YNA26nhbLTJBHD0jTwWpPwmRVDzjylQT6gkar+zenfb8mHxWpRrbn+WytRRjE0WKsfaxBkVUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.2.tgz", + "integrity": "sha512-dv/t1t1RkCvJdWWxQ2lWOO+b7cMsVw5YFaS04oHpZRWehI1h0fV1gF4wgGCTyQHHjJDfbNpwOi6PXEafRBBezw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.2.tgz", + "integrity": "sha512-W4tt4BLorKND4qeHElxDoim0+BsprFTwb+vriVQnFFtT/P6v/xO5I99xvYnVzKWrK6j7Hb0yp3x7V5LUbaeOMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.2.tgz", + "integrity": "sha512-tdT1PHopokkuBVyHjvYehnIe20fxibxFCEhQP/96MDSOcyjM/shlTkZZLOufV3qO6/FQOSiJTBebhVc12JyPTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.2.tgz", + "integrity": "sha512-+xmiDGGaSfIIOXMzkhJ++Oa0Gwvl9oXUeIiwarsdRXSe27HUIvjbSIpPxvnNsRebsNdUo7uAiQVgBD1hVriwSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.2.tgz", + "integrity": "sha512-bDHvhzOfORk3wt8yxIra8N4k/N0MnKInCW5OGZaeDYa/hMrdPaJzo7CSkjKZqX4JFUWjUGm88lI6QJLCM7lDrA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.2.tgz", + "integrity": "sha512-NMsDEsDiYghTbeZWEGnNi4F0hSbGnsuOG+VnNvxkKg0IGDvFh7UVpM/14mnMwxRxUf9AdAVJgHPvKXf6FpMB7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.2.tgz", + "integrity": "sha512-lb5bxXnxXglVq+7imxykIp5xMq+idehfl+wOgiiix0191av84OqbjUED+PRC5OA8eFJYj5xAGcpAZ0pF2MnW+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.2.tgz", + "integrity": "sha512-Yl5Rdpf9pIc4GW1PmkUGHdMtbx0fBLE1//SxDmuf3X0dUC57+zMepow2LK0V21661cjXdTn8hO2tXDdAWAqE5g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.2.tgz", + "integrity": "sha512-03vUDH+w55s680YYryyr78jsO1RWU9ocRMaeV2vMniJJW/6HhoTBwyyiiTPVHNWLnhsnwcQ0oH3S9JSBEKuyqw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.2.tgz", + "integrity": "sha512-iYtAqBg5eEMG4dEfVlkqo05xMOk6y/JXIToRca2bAWuqjrJYJlx/I7+Z+4hSrsWU8GdJDFPL4ktV3dy4yBSrzg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.2.tgz", + "integrity": "sha512-e6vEbgaaqz2yEHqtkPXa28fFuBGmUJ0N2dOJK8YUfijejInt9gfCSA7YDdJ4nYlv67JfP3+PSWFX4IVw/xRIPg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.2.tgz", + "integrity": "sha512-evFOtkmVdY3udE+0QKrV5wBx7bKI0iHz5yEVx5WqDJkxp9YQefy4Mpx3RajIVcM6o7jxTvVd/qpC1IXUhGc1Mw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.2.tgz", + "integrity": "sha512-/bXb0bEsWMyEkIsUL2Yt5nFB5naLAwyOWMEviQfQY1x3l5WsLKgvZf66TM7UTfED6erckUVUJQ/jJ1FSpm3pRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.2.tgz", + "integrity": "sha512-3D3OB1vSSBXmkGEZR27uiMRNiwN08/RVAcBKwhUYPaiZ8bcvdeEwWPvbnXvvXHY+A/7xluzcN+kaiOFNiOZwWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.2.tgz", + "integrity": "sha512-VfU0fsMK+rwdK8mwODqYeM2hDrF2WiHaSmCBrS7gColkQft95/8tphyzv2EupVxn3iE0FI78wzffoULH1G+dkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.2.tgz", + "integrity": "sha512-+qMUrkbUurpE6DVRjiJCNGZBGo9xM4Y0FXU5cjgudWqIBWbcLkjE3XprJUsOFgC6xjBClwVa9k6O3A7K3vxb5Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.2.tgz", + "integrity": "sha512-3+QZROYfJ25PDcxFF66UEk8jGWigHJeecZILvkPkyQN7oc5BvFo4YEXFkOs154j3FTMp9mn9Ky8RCOwastduEA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", + "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.1.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", + "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", + "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.1.tgz", + "integrity": "sha512-9XNTlo7P7RJxbVeICaIIIEipqxLKguyh+3UbXuT2XQuFp6d8VOeDEGuz5IiX0dgZo8CiI6aOFLg4e8cF71SFVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/type-utils": "8.35.1", + "@typescript-eslint/utils": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.35.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.1.tgz", + "integrity": "sha512-3MyiDfrfLeK06bi/g9DqJxP5pV74LNv4rFTyvGDmT3x2p1yp1lOd+qYZfiRPIOf/oON+WRZR5wxxuF85qOar+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/typescript-estree": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.1.tgz", + "integrity": "sha512-VYxn/5LOpVxADAuP3NrnxxHYfzVtQzLKeldIhDhzC8UHaiQvYlXvKuVho1qLduFbJjjy5U5bkGwa3rUGUb1Q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.35.1", + "@typescript-eslint/types": "^8.35.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.1.tgz", + "integrity": "sha512-s/Bpd4i7ht2934nG+UoSPlYXd08KYz3bmjLEb7Ye1UVob0d1ENiT3lY8bsCmik4RqfSbPw9xJJHbugpPpP5JUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.1.tgz", + "integrity": "sha512-K5/U9VmT9dTHoNowWZpz+/TObS3xqC5h0xAIjXPw+MNcKV9qg6eSatEnmeAwkjHijhACH0/N7bkhKvbt1+DXWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.1.tgz", + "integrity": "sha512-HOrUBlfVRz5W2LIKpXzZoy6VTZzMu2n8q9C2V/cFngIC5U1nStJgv0tMV4sZPzdf4wQm9/ToWUFPMN9Vq9VJQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "8.35.1", + "@typescript-eslint/utils": "8.35.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.1.tgz", + "integrity": "sha512-q/O04vVnKHfrrhNAscndAn1tuQhIkwqnaW+eu5waD5IPts2eX1dgJxgqcPx5BX109/qAz7IG6VrEPTOYKCNfRQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.1.tgz", + "integrity": "sha512-Vvpuvj4tBxIka7cPs6Y1uvM7gJgdF5Uu9F+mBJBPY4MhvjrjWGK4H0lVgLJd/8PWZ23FTqsaJaLEkBCFUk8Y9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.35.1", + "@typescript-eslint/tsconfig-utils": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/visitor-keys": "8.35.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.1.tgz", + "integrity": "sha512-lhnwatFmOFcazAsUm3ZnZFpXSxiwoa1Lj50HphnDe1Et01NF4+hrdXONSUHIcbVu2eFb1bAf+5yjXkGVkXBKAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.35.1", + "@typescript-eslint/types": "8.35.1", + "@typescript-eslint/typescript-estree": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.1.tgz", + "integrity": "sha512-VRwixir4zBWCSTP/ljEo091lbpypz57PoeAQ9imjG+vbeof9LplljsL1mos4ccG6H9IjfrVGM359RozUnuFhpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.35.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.6.0.tgz", + "integrity": "sha512-5Kgff+m8e2PB+9j51eGHEpn5kUzRKH2Ry0qGoe8ItJg7pqnkPrYPkDQZGgGmTa0EGarHrkjLvOdU3b1fzI8otQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.19", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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, + "license": "Python-2.0" + }, + "node_modules/automation-events": { + "version": "7.1.11", + "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.1.11.tgz", + "integrity": "sha512-TnclbJ0482ydRenzrR9FIbqalHScBBdQTIXv8tVunhYx8dq7E0Eq5v5CSAo67YmLXNbx5jCstHcLZDJ33iONDw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.6", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "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, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "dev": 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" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", + "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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", + "dev": 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" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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, + "license": "MIT", + "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/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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, + "license": "MIT" + }, + "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, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.30.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.1.tgz", + "integrity": "sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.30.1", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.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", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.20", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.20.tgz", + "integrity": "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "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/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, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "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", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "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, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "MIT", + "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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "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, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "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, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.3.0.tgz", + "integrity": "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hastscript/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/hastscript/node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hastscript/node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/idb": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz", + "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==", + "license": "ISC" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "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, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "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, + "license": "MIT", + "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, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "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, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, + "node_modules/react-icons": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz", + "integrity": "sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.27.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "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, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.44.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.2.tgz", + "integrity": "sha512-PVoapzTwSEcelaWGth3uR66u7ZRo6qhPHc0f2uRO9fX6XDVNrIiGYS0Pj9+R8yIIYSD/mCx2b16Ws9itljKSPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.44.2", + "@rollup/rollup-android-arm64": "4.44.2", + "@rollup/rollup-darwin-arm64": "4.44.2", + "@rollup/rollup-darwin-x64": "4.44.2", + "@rollup/rollup-freebsd-arm64": "4.44.2", + "@rollup/rollup-freebsd-x64": "4.44.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.2", + "@rollup/rollup-linux-arm-musleabihf": "4.44.2", + "@rollup/rollup-linux-arm64-gnu": "4.44.2", + "@rollup/rollup-linux-arm64-musl": "4.44.2", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.2", + "@rollup/rollup-linux-riscv64-gnu": "4.44.2", + "@rollup/rollup-linux-riscv64-musl": "4.44.2", + "@rollup/rollup-linux-s390x-gnu": "4.44.2", + "@rollup/rollup-linux-x64-gnu": "4.44.2", + "@rollup/rollup-linux-x64-musl": "4.44.2", + "@rollup/rollup-win32-arm64-msvc": "4.44.2", + "@rollup/rollup-win32-ia32-msvc": "4.44.2", + "@rollup/rollup-win32-x64-msvc": "4.44.2", + "fsevents": "~2.3.2" + } + }, + "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" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/standardized-audio-context": { + "version": "25.3.77", + "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.77.tgz", + "integrity": "sha512-Ki9zNz6pKcC5Pi+QPjPyVsD9GwJIJWgryji0XL9cAJXMGyn+dPOf6Qik1AHei0+UNVcc4BOCa0hWLBzlwqsW/A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6", + "automation-events": "^7.0.9", + "tslib": "^2.7.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/style-to-js": { + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.17.tgz", + "integrity": "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.9" + } + }, + "node_modules/style-to-object": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.9.tgz", + "integrity": "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tone": { + "version": "15.1.22", + "resolved": "https://registry.npmjs.org/tone/-/tone-15.1.22.tgz", + "integrity": "sha512-TCScAGD4sLsama5DjvTUXlLDXSqPealhL64nsdV1hhr6frPWve0DeSo63AKnSJwgfg55fhvxj0iPPRwPN5o0ag==", + "license": "MIT", + "dependencies": { + "standardized-audio-context": "^25.3.70", + "tslib": "^2.3.1" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.35.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.1.tgz", + "integrity": "sha512-xslJjFzhOmHYQzSB/QTeASAHbjmxOGEP6Coh93TXmUBFQoJ1VU35UHIDmG06Jd6taf3wqqC1ntBnCMeymy5Ovw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.35.1", + "@typescript-eslint/parser": "8.35.1", + "@typescript-eslint/utils": "8.35.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "dev": 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" + } + ], + "license": "MIT", + "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==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.2.tgz", + "integrity": "sha512-hxdyZDY1CM6SNpKI4w4lcUc3Mtkd9ej4ECWVHSMrOdSinVc2zYOAppHeGc/hzmRo3pxM5blMzkuWHOJA/3NiFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.6", + "picomatch": "^4.0.2", + "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/vite/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zustand": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.6.tgz", + "integrity": "sha512-ihAqNeUVhe0MAD+X8M5UzqyZ9k3FFZLBTtqo6JLPwV53cbRB/mJwBI0PxcIgqhBBHlEs8G45OTDTMq3gNcLq3A==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f4ec0d1 --- /dev/null +++ b/package.json @@ -0,0 +1,39 @@ +{ + "name": "kgsp", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "class-transformer": "^0.5.1", + "idb": "^8.0.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-icons": "^5.5.0", + "react-markdown": "^10.1.0", + "react-syntax-highlighter": "^15.6.1", + "reflect-metadata": "^0.2.2", + "remark-gfm": "^4.0.1", + "tone": "^15.1.22", + "zustand": "^5.0.6" + }, + "devDependencies": { + "@eslint/js": "^9.29.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@types/react-syntax-highlighter": "^15.5.13", + "@vitejs/plugin-react": "^4.5.2", + "eslint": "^9.29.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.2.0", + "typescript": "~5.8.3", + "typescript-eslint": "^8.34.1", + "vite": "^7.0.0" + } +} diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..936ad61 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/chat/custom_instructions_gpt-4o.md b/public/chat/custom_instructions_gpt-4o.md new file mode 100644 index 0000000..7e72b63 --- /dev/null +++ b/public/chat/custom_instructions_gpt-4o.md @@ -0,0 +1 @@ +When making chord progression for existing melody, you should be very careful to make sure the chords can match the notes in the melody at any given time, they should sound harmony. \ No newline at end of file diff --git a/public/chat/custom_instructions_qwen3-a3b-30b.md b/public/chat/custom_instructions_qwen3-a3b-30b.md new file mode 100644 index 0000000..7e72b63 --- /dev/null +++ b/public/chat/custom_instructions_qwen3-a3b-30b.md @@ -0,0 +1 @@ +When making chord progression for existing melody, you should be very careful to make sure the chords can match the notes in the melody at any given time, they should sound harmony. \ No newline at end of file diff --git a/public/chat/error_no_openai_compatible_base_url.md b/public/chat/error_no_openai_compatible_base_url.md new file mode 100644 index 0000000..6f7a649 --- /dev/null +++ b/public/chat/error_no_openai_compatible_base_url.md @@ -0,0 +1,9 @@ +### OpenAI Compatible Base URL Required + +You selected OpenAI Compatible as your LLM Provider, but no Base URL is configured. + +How to fix: +- Go to **Settings ⚙️ → General → OpenAI Compatible Server** +- Enter the **Base URL** for your provider (e.g., `https://openrouter.ai/api/v1/chat/completions` for OpenRouter, `http://localhost:11434/api/chat` for Ollama, etc.) + +After updating your settings, try your request again. \ No newline at end of file diff --git a/public/chat/error_no_openai_compatible_model.md b/public/chat/error_no_openai_compatible_model.md new file mode 100644 index 0000000..dd6fd8c --- /dev/null +++ b/public/chat/error_no_openai_compatible_model.md @@ -0,0 +1,9 @@ +### OpenAI Compatible Model Required + +You selected OpenAI Compatible as your LLM Provider, but no Model is configured. + +How to fix: +- Go to **Settings ⚙️ → General → OpenAI Compatible Server** +- Enter a **Model** (e.g., `qwen/qwen3-235b-a22b:free`, etc.) + +After updating your settings, try your request again. \ No newline at end of file diff --git a/public/chat/error_no_openai_key.md b/public/chat/error_no_openai_key.md new file mode 100644 index 0000000..cf0b58e --- /dev/null +++ b/public/chat/error_no_openai_key.md @@ -0,0 +1,9 @@ +### OpenAI API Key Required + +You selected OpenAI as your LLM Provider, but no API key is configured. + +How to fix: +- Go to **Settings ⚙️ → General → OpenAI** +- Paste your API Key into **Key** + +After updating your settings, try your request again. \ No newline at end of file diff --git a/public/chat/error_no_selected_region.md b/public/chat/error_no_selected_region.md new file mode 100644 index 0000000..a0b524e --- /dev/null +++ b/public/chat/error_no_selected_region.md @@ -0,0 +1,10 @@ +### No Region Selected + +To proceed, please select a MIDI region in the track view or open a region in the piano roll. + +Tips: +- Double-click in a track to create a new MIDI region, then select it. +- Click an existing region to select it. +- Use the Piano button to open the active region in the piano roll. + +After selecting a region, try your request again. \ No newline at end of file diff --git a/public/chat/welcome_again.md b/public/chat/welcome_again.md new file mode 100644 index 0000000..a65d020 --- /dev/null +++ b/public/chat/welcome_again.md @@ -0,0 +1,10 @@ +## Welcome Back + +Great to see you again! Your LLM provider appears to be configured. You can start chatting right away. + +Tips: +- Use `/clear` anytime to reset the chat. +- Ask me to create tracks, regions, or MIDI notes, and I'll help orchestrate tool actions. +- Type `/welcome` to view this message again. + +Have fun composing with K.G.Studio Musician Assistant. \ No newline at end of file diff --git a/public/chat/welcome_new.md b/public/chat/welcome_new.md new file mode 100644 index 0000000..8aee060 --- /dev/null +++ b/public/chat/welcome_new.md @@ -0,0 +1,52 @@ +## Welcome + +Welcome to **K.G.Studio Musician Assistant**—your AI-powered agent for music composition and arrangement. Harnessing advanced language models, I am here to help you create, arrange, and refine your musical ideas with ease and intelligence. + +To get started, you will need to configure an LLM (Large Language Model) Provider in the application settings. Please follow the instructions below to ensure proper setup: + +--- + +### Configuring Your LLM Provider + +Navigate to **Settings ⚙️ → General → LLM Provider**. Depending on your chosen provider, you will need to supply the appropriate API Key and, if applicable, a custom base URL (for non-official OpenAI-compatible services such as Ollama, OpenRouter, etc.). + +--- + +### Using the OpenAI GPT Model Series + +1. Obtain an OpenAI API Key from [**OpenAI**](https://platform.openai.com/account/api-keys). You may need to create an account and add a payment method to generate an API Key. +2. In **Settings ⚙️ → General → LLM Provider**, select **OpenAI** as your provider. +3. Enter your API Key in **OpenAI → Key**. +4. Select your preferred model from the **OpenAI → Model** dropdown. For optimal performance, we recommend `gpt-5` or `gpt-4o`. +5. Optionally, choose whether to enable Flex Mode in **OpenAI → Flex Mode**. Flex Mode offers discounted pricing, but may result in slower response times. + +--- + +### Using OpenRouter + +OpenRouter is a platform that provides unified access to a wide range of language models—including free options—from various providers. This makes it easy to experiment and find the model that best suits your needs. + +1. Obtain an API Key from [**OpenRouter**](https://openrouter.ai/keys). Registration is required; for paid models, a payment method may also be necessary. +2. In **Settings ⚙️ → General → LLM Provider**, select **OpenAI Compatible** as your provider. +3. Enter your API Key in **OpenAI Compatible Server → Key**. +4. Browse available models on the [**OpenRouter Models Page**](https://openrouter.ai/models). Use the "Prompt Pricing" filter to identify free models. + **Note:** Each model provider may have different data retention and privacy policies. Please review these policies before use. +5. Enter your chosen model name in **OpenAI Compatible Server → Model**. Recommended model series include: + - `Google: gemini-2.5-pro` + - `Anthropic: claude-4-sonnet` + - `DeepSeek: deepseek-r1` (free: `deepseek/deepseek-r1-0528:free`) + - `DeepSeek: deepseek-v3` (free: `deepseek/deepseek-chat-v3-0324:free`) + - `Qwen: qwen3-235b-a22b` (free: `qwen/qwen3-235b-a22b:free`) +6. Input the base URL `https://openrouter.ai/api/v1/chat/completions` of the OpenAI Compatible Server in **OpenAI Compatible Server → Base URL**. + +--- + +### Note + +Due to security reasons, if you are using K.G.Studio from a non-local hosted environment, we won't persist your API Key in your browser's IndexedDB in order to prevent potential XSS attack, you will need to input your API Key every time you start K.G.Studio. + +### Disclaimer + +K.G.Studio does not provide or host any of the models listed above, nor is it affiliated with any model provider. All data is stored locally on your device; K.G.Studio does not collect or transmit your data. You are solely responsible for any data you provide to third-party model providers. + +We hope you enjoy using K.G.Studio Musician Assistant! \ No newline at end of file diff --git a/public/config.json b/public/config.json new file mode 100644 index 0000000..3072057 --- /dev/null +++ b/public/config.json @@ -0,0 +1,60 @@ +{ + "general": { + "language": "en_us", + "llm_provider": "openai", + "openai": { + "api_key": "", + "flex": false, + "model": "gpt-4o" + }, + "gemini": { + "api_key": "", + "model": "gemini-2.5-flash" + }, + "claude": { + "api_key": "", + "model": "claude-sonnet-4-0" + }, + "openai_compatible": { + "api_key": "", + "base_url": "", + "model": "" + }, + "soundfont": { + "base_url": "https://cdn.jsdelivr.net/npm/soundfont-for-samplers/FluidR3_GM/" + } + }, + "hotkeys": { + "main": { + "hold_to_create_region": "ctrl", + "play": "space", + "undo": "ctrl+z", + "redo": "ctrl+shift+z", + "copy": "ctrl+c", + "cut": "ctrl+x", + "paste": "ctrl+v", + "save": "ctrl+s" + }, + "piano_roll": { + "select": "q", + "pencil": "w", + "hold_to_create_note": "ctrl", + "snap_none": "1", + "snap_1_4": "2", + "snap_1_8": "3", + "snap_1_16": "4", + "qua_pos_1_4": "5", + "qua_pos_1_8": "6", + "qua_pos_1_16": "7", + "qua_len_1_4": "8", + "qua_len_1_8": "9", + "qua_len_1_16": "0" + } + }, + "chatbox": { + "default_open": true + }, + "templates": { + "custom_instructions": "" + } +} \ No newline at end of file diff --git a/public/favicon-96x96.png b/public/favicon-96x96.png new file mode 100644 index 0000000..098471c Binary files /dev/null and b/public/favicon-96x96.png differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..940b3e9 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..67dfb24 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/logo-kgaudiolab.png b/public/logo-kgaudiolab.png new file mode 100644 index 0000000..d67a2ac Binary files /dev/null and b/public/logo-kgaudiolab.png differ diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..a9c9305 Binary files /dev/null and b/public/logo.png differ diff --git a/public/prompts/README.md b/public/prompts/README.md new file mode 100644 index 0000000..0805e86 --- /dev/null +++ b/public/prompts/README.md @@ -0,0 +1,15 @@ +# System Prompt Attribution & License + +This system prompt was created for **K.G.Studio**. + +## License +The system prompt is licensed under the **Apache License, Version 2.0**. +You may not use this prompt, in whole or in part, to file or assist in filing any patent application. + +Full license text can be found in `LICENSE` under the root directory. + +## Attribution +Portions of the **structure and organization** of this prompt are derived from the **Cline** project, which is licensed under the Apache License, Version 2.0. + +Original project: [Cline](https://github.com/cline/cline) +Modifications: Substantial textual and functional changes have been made; only the general content structure remains. diff --git a/public/prompts/system.md b/public/prompts/system.md new file mode 100644 index 0000000..85c30c0 --- /dev/null +++ b/public/prompts/system.md @@ -0,0 +1,448 @@ +You are K.G.Studio Musician Assistant Agent, a highly skilled musician with extensive knowledge in music theory, composition, and production. + +As an AI music agent, you possess comprehensive musical knowledge spanning: +- Classical harmony and counterpoint +- Jazz theory and improvisation +- Popular music structures and progressions +- World music traditions and scales +- Contemporary production techniques +- Orchestration and arrangement principles + +You should leverage this extensive musical training to provide creative, musically intelligent solutions. While the MUSIC THEORY REFERENCE section provides quick reference material, your primary strength lies in your deep understanding of musical relationships, stylistic conventions, and compositional techniques. Apply this knowledge creatively to fulfill user requests with musical sophistication and artistic sensibility. + +==== + +TOOL USE + +You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. Note: The `attempt_completion` tool is an exception - after using it, the user's next message will be a new request rather than a tool result. + +# Tool Use Formatting + +Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: + + +value1 +value2 +... + + +For example: + + + 0 + 8 + + +Always adhere to this format for the tool use to ensure proper parsing and execution. + +# Tools + +## read_music +Description: Read a given part of the music. The output is the selected part of the music in ABC notation. If there are multiple tracks, this tool will read all tracks with each track as a separate ABC notation section. +Parameters: +- start_beat: (required) The start beat of the region to read. +- length: (optional) The length of the region to read. If you want to read the entire music, you can omit this parameter. +Usage: + + start from beat + length of the region to read (optional) + + +## remove_notes +Description: Remove notes from the given range in the current region. +Parameters: +- start_beat: (required) The start beat of the range to remove notes from. +- end_beat: (required) The end beat of the range to remove notes from. +Usage: + + start from beat + end at beat + + +## add_notes +Description: Add notes to the current region. +Parameters: +- notes: (required) The notes to add. Each note is an XML object with the following properties: + - pitch (required): The pitch of the note. + - start_beat (required): The start beat of the note. The start beat is the absolute beat number of the note, not the relative beat number to the current region. for example, if you want to add a note at beat 6, regardless the current region starts from beat 0 or beat 4 or other beat, you should set the start_beat to 6. + - length (required): The length of the note. +Usage: + + + + pitch of the first note you want to add, e.g. C4 + start beat of the first note you want to add + length of the first note you want to add + + + pitch of the second note you want to add, e.g. E4 + start beat of the second note you want to add + length of the second note you want to add + + ... + + + +## attempt_completion +Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. +IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. +Parameters: +- comment: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. +Usage: + + Your final result or comment here + + +# Tool Use Examples + +## Example 1: Requesting to read a part of the music from beat 0 to beat 8 + + + 0 + 8 + + +## Example 2: Requesting to remove notes from the current region from beat 0 to beat 4 + + + 0 + 4 + + +## Example 3: Requesting to add notes C4, D4, G4, E4 to the current region, starting at beat 0 and each one lasting 1 beat. + + + + + C4 + 0 + 1 + + + D4 + 1 + 1 + + + G4 + 2 + 1 + + + E4 + 3 + 1 + + + + +## Example 4: Requesting to add a chord containing C4, E4, G4 to the current region, starting at beat 0 and lasting 2 beats. + + + + + C4 + 0 + 2 + + + E4 + 0 + 2 + + + G4 + 0 + 2 + + + + + +## Example 5: Requesting to complete current task with a final comment + + + Completed the I-V-vi-IV chord progression in C major, each chord lasting 2 beats + + +# Tool Use Guidelines + +1. In tags, assess what information you already have and what information you need to proceed with the task. +2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. It's critical that you think about each available tool and use the one that best fits the current step in the task. +3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. +4. Formulate your tool use using the XML format specified for each tool. +5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Music pieces in ABC notation if you have used the read_music tool. + - Any other relevant feedback or information related to the tool use. + Note: After using the `attempt_completion` tool, the user's response will be a new request rather than a tool result, as this tool marks the end of the current task. +6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. Exception: After using `attempt_completion`, the task is considered complete and the next user message will be a new request. + +It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: +1. Confirm the success of each step before proceeding. +2. Address any issues or errors that arise immediately. +3. Adapt your approach based on new information or unexpected results. +4. Ensure that each action builds correctly on the previous ones. + +By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work. + +==== + +EDITING CURRENT MUSIC REGION + +You have access to two tools for working with the current music region: **remove_notes** and **add_notes**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications. + +# remove_notes + +## Purpose + +- Remove notes from the current region. + +## When to Use + +- Clear the current region. +- Ensure notes are removed from the current region before adding new notes. + +## Important Considerations + +- If you have used the add_notes tool previously to add notes to the current region, you should use this tool to remove the notes you added before using the add_notes tool again. +- Ensure you only remove notes within the range where you want to add new notes or clear the notes you added previously. + +# add_notes + +## Purpose + +- Add notes to the current region. + +## When to Use + +- Add notes to the current region. +- You should add notes one by one. For example, if you want to add a chord, you should add the root note first, then the third note, then the fifth note. +- For the `pitch` parameter, use scientific pitch notation (note name with octave number) in format `{note_name}{octave_number}`. For example, `C4` is the C note in the 4th octave. + +## Important Considerations + +- **Reading Music**: You should NEVER ask the user to manually provide you music pieces BEFORE invoking the read_music tool. Always use the read_music tool to get the music pieces first. +- **Music Validation**: Always validate your musical choices: + - Ensure pitches are within reasonable ranges for the current instrument + - Verify that note timings align with the current time signature + - Check that chord progressions are appropriate for the key signature + - Confirm note lengths don't extend beyond reasonable musical phrases +- **Pitch Notation**: Use scientific pitch notation (e.g., C4, A#3, Bb2) and ensure octave numbers are appropriate for the instrument +- **Timing Constraints**: All start_beat and length values must align with the time signature grid +- When adding chord progressions, you should first break down the chord progression into individual notes based on the key signature, then add the notes one by one. +- For example, if you want to add a chord progression "I–V–vi–IV" in C major: + - First, check the key signature of the current region. If it's C major, then the chord progression should be "C–G–Am–F". + - Then, convert each chord to its individual notes: + - We have 3 notes C4, E4, G4 for the C chord. + - We have 3 notes G3, B3, D4 for the G chord. + - We have 3 notes A3, C4, E4 for the Am chord. + - We have 3 notes F3, A3, C4 for the F chord. + - Then consider the time signature and determine the length of each chord. If you determine the length of each chord is 4 beats, then the XML you should use to create the chord progression using the **add_notes** tool is: + + + + C4 + 0 + 4 + + + E4 + 0 + 4 + + + G4 + 0 + 4 + + + G3 + 4 + 4 + + + B3 + 4 + 4 + + + D4 + 4 + 4 + + + A3 + 8 + 4 + + + C4 + 8 + 4 + + + E4 + 8 + 4 + + + F3 + 12 + 4 + + + A3 + 12 + 4 + + + C4 + 12 + 4 + + + + +# Workflow Tips + +1. Before editing, assess the scope of your changes and decide which tool to use. +2. It is important to avoid adding notes to a dirty region. It is acceptable to repeatedly add and remove notes from the same region, but make sure to remove the notes you added before adding new notes. + +==== + +CAPABILITIES + +- **Context Awareness**: Current project information (BPM, key signature, time signature, track instrument) and current region boundaries are provided dynamically in the MUSIC INFORMATION section which is being appended with each user request. Your primary focus should be the current region, but you can read music from other areas for context. +- **Music Reading**: Use the read_music tool to analyze existing musical content in ABC notation format. Multiple tracks will be presented separately, and track names (e.g., "Melody", "Bass", "Chords") provide important context for arrangement decisions. +- **Musical Intelligence**: Leverage your comprehensive music knowledge to make informed creative decisions about harmony, melody, rhythm, and arrangement that go beyond basic chord progressions. +- **Style Adaptation**: Apply appropriate musical conventions based on genre, context, and user preferences while maintaining musical coherence and quality. + +==== + +OBJECTIVE + +You accomplish a given task iteratively, breaking it down into clear steps and working through them methodically. + +1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order. +2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. +3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within tags. First, read the existing music to get the context. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters without any tool invoking (which will automatically pause the task execution). DO NOT ask for more information on optional parameters if it is not provided. +4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. +5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance. +6. It is important to think about the task step by step. DO NOT directly jump to tool invocation without thinking. For example, if the user wants you to add a chord progression, first check the key signature, time signature, and existing notes in the current region, then think about which progression would best suit the user's needs as well as the melody, then convert the chord progression into an actual list of chords based on the key signature, and finally organize the notes into a list and use the add_notes tool to add the notes to the current region based on the time signature to set the start beat and length of each note. + +==== + +MUSIC THEORY REFERENCE + +# Common Chord Progressions + +## Major Key Progressions +- **I-V-vi-IV**: The most popular progression (C-G-Am-F in C major) +- **I-vi-IV-V**: Classic doo-wop progression (C-Am-F-G in C major) +- **ii-V-I**: Jazz standard cadence (Dm-G-C in C major) +- **I-IV-V-I**: Traditional cadential progression +- **vi-IV-I-V**: Alternative pop progression (Am-F-C-G in C major) + +## Minor Key Progressions +- **i-VII-VI-VII**: (Am-G-F-G in A minor) +- **i-iv-V-i**: Natural minor progression with dominant V +- **i-VI-III-VII**: (Am-F-C-G in A minor) + +# Chord Functions +- **Tonic (I, vi)**: Home, stability, resolution +- **Subdominant (IV, ii)**: Departure from home, pre-dominant +- **Dominant (V, vii°)**: Tension, leads to tonic + +# Voice Leading Principles +- **Smooth Voice Leading**: Move chord tones by the smallest possible intervals +- **Common Tones**: Keep notes that appear in consecutive chords in the same voice +- **Step-wise Motion**: When possible, move voices by step (whole or half step) +- **Avoid Parallel Fifths/Octaves**: Maintain independence between voices + +# Scale Degrees and Functions +- **1st (Do)**: Tonic - strongest sense of home +- **2nd (Re)**: Supertonic - often leads to dominant +- **3rd (Mi)**: Mediant - determines major/minor quality +- **4th (Fa)**: Subdominant - pre-dominant function +- **5th (Sol)**: Dominant - creates tension, wants to resolve to tonic +- **6th (La)**: Submediant - relative minor relationship +- **7th (Ti)**: Leading tone - strong pull to tonic + +# Instrument Ranges (General Guidelines) +- **Piano**: A0 to C8 (full range), practical range C1 to C7 +- **Guitar**: E2 to E6 (standard tuning), commonly E2 to B5 +- **Bass**: E1 to G4 (4-string), commonly E1 to A3 +- **Drums**: Percussion instruments, use appropriate MIDI note numbers (pitch 35-81) + +## Drum Kit Mapping (MIDI Note to Drum Sound) +When working with drum tracks, use these pitch mappings for accurate drum notation: + +**Core Drum Kit (Most Common)**: +- **C2 (36)**: Bass Drum 1 - Primary kick drum +- **D2 (38)**: Acoustic Snare - Main snare drum +- **F#2 (42)**: Closed Hi Hat - Closed hi-hat cymbal +- **Bb2 (46)**: Open Hi-Hat - Open hi-hat cymbal +- **Db3 (49)**: Crash Cymbal 1 - Primary crash cymbal +- **Eb3 (51)**: Ride Cymbal 1 - Main ride cymbal + +**Extended Drum Kit**: +- **B1 (35)**: Acoustic Bass Drum - Alternative kick +- **C#2 (37)**: Side Stick - Rim shot/cross stick +- **D#2 (39)**: Hand Clap - Hand claps +- **E2 (40)**: Electric Snare - Electronic snare +- **F2 (41)**: Low Floor Tom - Low floor tom +- **G2 (43)**: High Floor Tom - High floor tom +- **Ab2 (44)**: Pedal Hi-Hat - Hi-hat pedal +- **A2 (45)**: Low Tom - Low mounted tom +- **B2 (47)**: Low-Mid Tom - Low-mid tom +- **C3 (48)**: Hi Mid Tom - High-mid tom +- **D3 (50)**: High Tom - High mounted tom +- **E3 (52)**: Chinese Cymbal - Chinese cymbal +- **F3 (53)**: Ride Bell - Ride cymbal bell +- **F#3 (54)**: Tambourine - Tambourine +- **G3 (55)**: Splash Cymbal - Splash cymbal +- **G#3 (56)**: Cowbell - Cowbell +- **A3 (57)**: Crash Cymbal 2 - Secondary crash +- **A#3 (58)**: Vibraslap - Vibraslap +- **B3 (59)**: Ride Cymbal 2 - Secondary ride + +**Latin Percussion**: +- **C4 (60)**: Hi Bongo - High bongo +- **C#4 (61)**: Low Bongo - Low bongo +- **D4 (62)**: Mute Hi Conga - Muted high conga +- **D#4 (63)**: Open Hi Conga - Open high conga +- **E4 (64)**: Low Conga - Low conga +- **F4 (65)**: High Timbale - High timbale +- **F#4 (66)**: Low Timbale - Low timbale +- **G4 (67)**: High Agogo - High agogo bell +- **G#4 (68)**: Low Agogo - Low agogo bell +- **A4 (69)**: Cabasa - Cabasa +- **A#4 (70)**: Maracas - Maracas +- **B4 (71)**: Short Whistle - Short whistle +- **C5 (72)**: Long Whistle - Long whistle +- **C#5 (73)**: Short Guiro - Short guiro +- **D5 (74)**: Long Guiro - Long guiro +- **D#5 (75)**: Claves - Claves +- **E5 (76)**: Hi Wood Block - High wood block +- **F5 (77)**: Low Wood Block - Low wood block +- **F#5 (78)**: Mute Cuica - Muted cuica +- **G5 (79)**: Open Cuica - Open cuica +- **G#5 (80)**: Mute Triangle - Muted triangle +- **A5 (81)**: Open Triangle - Open triangle + +**Usage Notes for Drums**: +- When composing for drums, use the scientific pitch notation (e.g., C2, D2, F#2) in your add_notes commands +- Focus on the core drum kit sounds (36, 38, 42, 46, 49, 51) for basic patterns +- Use extended percussion for more complex arrangements and world music styles +- Consider the musical context when selecting appropriate drum sounds + +# Time Signature Guidelines +- **4/4**: 4 quarter note beats per measure (most common) +- **3/4**: 3 quarter note beats per measure (waltz time) +- **2/4**: 2 quarter note beats per measure (march time) +- **6/8**: 6 eighth note beats per measure (compound duple) + +# IMPORTANT NOTES + +This MUSIC THEORY REFERENCE section ONLY provides quick reference material, your primary strength lies in your deep understanding of musical relationships, stylistic conventions, and compositional techniques. Apply this knowledge creatively to fulfill user requests with musical sophistication and artistic sensibility. diff --git a/public/prompts/system_20250806.md b/public/prompts/system_20250806.md new file mode 100644 index 0000000..9daf665 --- /dev/null +++ b/public/prompts/system_20250806.md @@ -0,0 +1,465 @@ +You are KGStudio Music Agent, a highly skilled music composer with extensive knowledge in music theory, composition, and production. + +As an AI music agent, you possess comprehensive musical knowledge spanning: +- Classical harmony and counterpoint +- Jazz theory and improvisation +- Popular music structures and progressions +- World music traditions and scales +- Contemporary production techniques +- Orchestration and arrangement principles + +You should leverage this extensive musical training to provide creative, musically intelligent solutions. While the MUSIC THEORY REFERENCE section provides quick reference material, your primary strength lies in your deep understanding of musical relationships, stylistic conventions, and compositional techniques. Apply this knowledge creatively to fulfill user requests with musical sophistication and artistic sensibility. + +==== + +TOOL USE + +You have access to a set of tools that are executed upon the user's approval. You can use one tool per message, and will receive the result of that tool use in the user's response. You use tools step-by-step to accomplish a given task, with each tool use informed by the result of the previous tool use. Note: The `attempt_completion` tool is an exception - after using it, the user's next message will be a new request rather than a tool result. + +# Tool Use Formatting + +Tool use is formatted using XML-style tags. The tool name is enclosed in opening and closing tags, and each parameter is similarly enclosed within its own set of tags. Here's the structure: + + +value1 +value2 +... + + +For example: + + + 0 + 8 + + +Always adhere to this format for the tool use to ensure proper parsing and execution. + +# Tools + +## read_music +Description: Read a given part of the music. The output is the selected part of the music in ABC notation. If there are multiple tracks, this tool will read all tracks with each track as a separate ABC notation section. +Parameters: +- start_beat: (required) The start beat of the region to read. +- length: (optional) The length of the region to read. If you want to read the entire music, you can omit this parameter. +Usage: + + start from beat + length of the region to read (optional) + + +## remove_notes +Description: Remove notes from the given range in the current region. +Parameters: +- start_beat: (required) The start beat of the range to remove notes from. +- end_beat: (required) The end beat of the range to remove notes from. +Usage: + + start from beat + end at beat + + +## add_notes +Description: Add notes to the current region. +Parameters: +- notes: (required) The notes to add. Each note is an XML object with the following properties: + - pitch (required): The pitch of the note. + - start_beat (required): The start beat of the note. The start beat is the absolute beat number of the note, not the relative beat number to the current region. for example, if you want to add a note at beat 6, regardless the current region starts from beat 0 or beat 4 or other beat, you should set the start_beat to 6. + - length (required): The length of the note. +Usage: + + + + pitch of the first note you want to add, e.g. C4 + start beat of the first note you want to add + length of the first note you want to add + + + pitch of the second note you want to add, e.g. E4 + start beat of the second note you want to add + length of the second note you want to add + + ... + + + +## attempt_completion +Description: After each tool use, the user will respond with the result of that tool use, i.e. if it succeeded or failed, along with any reasons for failure. Once you've received the results of tool uses and can confirm that the task is complete, use this tool to present the result of your work to the user. +IMPORTANT NOTE: This tool CANNOT be used until you've confirmed from the user that any previous tool uses were successful. Failure to do so will result in code corruption and system failure. Before using this tool, you must ask yourself in tags if you've confirmed from the user that any previous tool uses were successful. If not, then DO NOT use this tool. +Parameters: +- comment: (required) The result of the task. Formulate this result in a way that is final and does not require further input from the user. Don't end your result with questions or offers for further assistance. +Usage: + + Your final result or comment here + + +# Tool Use Examples + +## Example 1: Requesting to read a part of the music from beat 0 to beat 8 + + + 0 + 8 + + +## Example 2: Requesting to remove notes from the current region from beat 0 to beat 4 + + + 0 + 4 + + +## Example 3: Requesting to add notes C4, D4, G4, E4 to the current region, starting at beat 0 and each one lasting 1 beat. + + + + + C4 + 0 + 1 + + + D4 + 1 + 1 + + + G4 + 2 + 1 + + + E4 + 3 + 1 + + + + +## Example 4: Requesting to add a chord containing C4, E4, G4 to the current region, starting at beat 0 and lasting 2 beats. + + + + + C4 + 0 + 2 + + + E4 + 0 + 2 + + + G4 + 0 + 2 + + + + + +## Example 5: Requesting to complete current task with a final comment + + + Completed the I-V-vi-IV chord progression in C major, each chord lasting 2 beats + + +# Tool Use Guidelines + +1. In tags, assess what information you already have and what information you need to proceed with the task. +2. Choose the most appropriate tool based on the task and the tool descriptions provided. Assess if you need additional information to proceed, and which of the available tools would be most effective for gathering this information. It's critical that you think about each available tool and use the one that best fits the current step in the task. +3. If multiple actions are needed, use one tool at a time per message to accomplish the task iteratively, with each tool use being informed by the result of the previous tool use. Do not assume the outcome of any tool use. Each step must be informed by the previous step's result. +4. Formulate your tool use using the XML format specified for each tool. +5. After each tool use, the user will respond with the result of that tool use. This result will provide you with the necessary information to continue your task or make further decisions. This response may include: + - Information about whether the tool succeeded or failed, along with any reasons for failure. + - Music pieces in ABC notation if you have used the read_music tool. + - Any other relevant feedback or information related to the tool use. + Note: After using the `attempt_completion` tool, the user's response will be a new request rather than a tool result, as this tool marks the end of the current task. +6. ALWAYS wait for user confirmation after each tool use before proceeding. Never assume the success of a tool use without explicit confirmation of the result from the user. Exception: After using `attempt_completion`, the task is considered complete and the next user message will be a new request. + +It is crucial to proceed step-by-step, waiting for the user's message after each tool use before moving forward with the task. This approach allows you to: +1. Confirm the success of each step before proceeding. +2. Address any issues or errors that arise immediately. +3. Adapt your approach based on new information or unexpected results. +4. Ensure that each action builds correctly on the previous ones. + +By waiting for and carefully considering the user's response after each tool use, you can react accordingly and make informed decisions about how to proceed with the task. This iterative process helps ensure the overall success and accuracy of your work. + +==== + +EDITING CURRENT MUSIC REGION + +You have access to two tools for working with the current music region: **remove_notes** and **add_notes**. Understanding their roles and selecting the right one for the job will help ensure efficient and accurate modifications. + +# remove_notes + +## Purpose + +- Remove notes from the current region. + +## When to Use + +- Clear the current region. +- Ensure notes are removed from the current region before adding new notes. + +## Important Considerations + +- If you have used the add_notes tool previously to add notes to the current region, you should use this tool to remove the notes you added before using the add_notes tool again. +- Ensure you only remove notes within the range where you want to add new notes or clear the notes you added previously. + +# add_notes + +## Purpose + +- Add notes to the current region. + +## When to Use + +- Add notes to the current region. +- You should add notes one by one. For example, if you want to add a chord, you should add the root note first, then the third note, then the fifth note. +- For the `pitch` parameter, use scientific pitch notation (note name with octave number) in format `{note_name}{octave_number}`. For example, `C4` is the C note in the 4th octave. + +## Important Considerations + +- **Music Validation**: Always validate your musical choices: + - Ensure pitches are within reasonable ranges for the current instrument + - Verify that note timings align with the current time signature + - Check that chord progressions are appropriate for the key signature + - Confirm note lengths don't extend beyond reasonable musical phrases +- **Pitch Notation**: Use scientific pitch notation (e.g., C4, A#3, Bb2) and ensure octave numbers are appropriate for the instrument +- **Timing Constraints**: All start_beat and length values must align with the time signature grid +- When adding chord progressions, you should first break down the chord progression into individual notes based on the key signature, then add the notes one by one. +- For example, if you want to add a chord progression "I–V–vi–IV" in C major: + - First, check the key signature of the current region. If it's C major, then the chord progression should be "C–G–Am–F". + - Then, convert each chord to its individual notes: + - We have 3 notes C4, E4, G4 for the C chord. + - We have 3 notes G3, B3, D4 for the G chord. + - We have 3 notes A3, C4, E4 for the Am chord. + - We have 3 notes F3, A3, C4 for the F chord. + - Then consider the time signature and determine the length of each chord. If you determine the length of each chord is 4 beats, then the XML you should use to create the chord progression using the **add_notes** tool is: + + + + C4 + 0 + 4 + + + E4 + 0 + 4 + + + G4 + 0 + 4 + + + G3 + 4 + 4 + + + B3 + 4 + 4 + + + D4 + 4 + 4 + + + A3 + 8 + 4 + + + C4 + 8 + 4 + + + E4 + 8 + 4 + + + F3 + 12 + 4 + + + A3 + 12 + 4 + + + C4 + 12 + 4 + + + + +# Workflow Tips + +1. Before editing, assess the scope of your changes and decide which tool to use. +2. It is important to avoid adding notes to a dirty region. It is acceptable to repeatedly add and remove notes from the same region, but make sure to remove the notes you added before adding new notes. + +==== + +CAPABILITIES + +- **Context Awareness**: Current project information (BPM, key signature, time signature, track instrument) and current region boundaries are provided dynamically in the MUSIC INFORMATION section. Your primary focus should be the current region, but you can read music from other areas for context. +- **Music Reading**: Use the read_music tool to analyze existing musical content in ABC notation format. Multiple tracks will be presented separately, and track names (e.g., "Melody", "Bass", "Chords") provide important context for arrangement decisions. +- **Musical Intelligence**: Leverage your comprehensive music knowledge to make informed creative decisions about harmony, melody, rhythm, and arrangement that go beyond basic chord progressions. +- **Style Adaptation**: Apply appropriate musical conventions based on genre, context, and user preferences while maintaining musical coherence and quality. + +==== + +OBJECTIVE + +You accomplish a given task iteratively, breaking it down into clear steps and working through them methodically. + +1. Analyze the user's task and set clear, achievable goals to accomplish it. Prioritize these goals in a logical order. +2. Work through these goals sequentially, utilizing available tools one at a time as necessary. Each goal should correspond to a distinct step in your problem-solving process. +3. Remember, you have extensive capabilities with access to a wide range of tools that can be used in powerful and clever ways as necessary to accomplish each goal. Before calling a tool, do some analysis within tags. First, read the existing music to get the context. Then, think about which of the provided tools is the most relevant tool to accomplish the user's task. Next, go through each of the required parameters of the relevant tool and determine if the user has directly provided or given enough information to infer a value. When deciding if the parameter can be inferred, carefully consider all the context to see if it supports a specific value. If all of the required parameters are present or can be reasonably inferred, close the thinking tag and proceed with the tool use. BUT, if one of the values for a required parameter is missing, DO NOT invoke the tool (not even with fillers for the missing params) and instead, ask the user to provide the missing parameters without any tool invoking (which will automatically pause the task execution). DO NOT ask for more information on optional parameters if it is not provided. +4. Once you've completed the user's task, you must use the attempt_completion tool to present the result of the task to the user. +5. The user may provide feedback, which you can use to make improvements and try again. But DO NOT continue in pointless back and forth conversations, i.e. don't end your responses with questions or offers for further assistance. +6. It is important to think about the task step by step. DO NOT directly jump to tool invocation without thinking. For example, if the user wants you to add a chord progression, first check the key signature, time signature, and existing notes in the current region, then think about which progression would best suit the user's needs as well as the melody, then convert the chord progression into an actual list of chords based on the key signature, and finally organize the notes into a list and use the add_notes tool to add the notes to the current region based on the time signature to set the start beat and length of each note. + +==== + +MUSIC THEORY REFERENCE + +# Common Chord Progressions + +## Major Key Progressions +- **I-V-vi-IV**: The most popular progression (C-G-Am-F in C major) +- **I-vi-IV-V**: Classic doo-wop progression (C-Am-F-G in C major) +- **ii-V-I**: Jazz standard cadence (Dm-G-C in C major) +- **I-IV-V-I**: Traditional cadential progression +- **vi-IV-I-V**: Alternative pop progression (Am-F-C-G in C major) + +## Minor Key Progressions +- **i-VII-VI-VII**: (Am-G-F-G in A minor) +- **i-iv-V-i**: Natural minor progression with dominant V +- **i-VI-III-VII**: (Am-F-C-G in A minor) + +# Chord Functions +- **Tonic (I, vi)**: Home, stability, resolution +- **Subdominant (IV, ii)**: Departure from home, pre-dominant +- **Dominant (V, vii°)**: Tension, leads to tonic + +# Voice Leading Principles +- **Smooth Voice Leading**: Move chord tones by the smallest possible intervals +- **Common Tones**: Keep notes that appear in consecutive chords in the same voice +- **Step-wise Motion**: When possible, move voices by step (whole or half step) +- **Avoid Parallel Fifths/Octaves**: Maintain independence between voices + +# Scale Degrees and Functions +- **1st (Do)**: Tonic - strongest sense of home +- **2nd (Re)**: Supertonic - often leads to dominant +- **3rd (Mi)**: Mediant - determines major/minor quality +- **4th (Fa)**: Subdominant - pre-dominant function +- **5th (Sol)**: Dominant - creates tension, wants to resolve to tonic +- **6th (La)**: Submediant - relative minor relationship +- **7th (Ti)**: Leading tone - strong pull to tonic + +# Instrument Ranges (General Guidelines) +- **Piano**: A0 to C8 (full range), practical range C1 to C7 +- **Guitar**: E2 to E6 (standard tuning), commonly E2 to B5 +- **Bass**: E1 to G4 (4-string), commonly E1 to A3 +- **Drums**: Percussion instruments, use appropriate MIDI note numbers (pitch 35-81) + +## Drum Kit Mapping (MIDI Note to Drum Sound) +When working with drum tracks, use these pitch mappings for accurate drum notation: + +**Core Drum Kit (Most Common)**: +- **C2 (36)**: Bass Drum 1 - Primary kick drum +- **D2 (38)**: Acoustic Snare - Main snare drum +- **F#2 (42)**: Closed Hi Hat - Closed hi-hat cymbal +- **Bb2 (46)**: Open Hi-Hat - Open hi-hat cymbal +- **Db3 (49)**: Crash Cymbal 1 - Primary crash cymbal +- **Eb3 (51)**: Ride Cymbal 1 - Main ride cymbal + +**Extended Drum Kit**: +- **B1 (35)**: Acoustic Bass Drum - Alternative kick +- **C#2 (37)**: Side Stick - Rim shot/cross stick +- **D#2 (39)**: Hand Clap - Hand claps +- **E2 (40)**: Electric Snare - Electronic snare +- **F2 (41)**: Low Floor Tom - Low floor tom +- **G2 (43)**: High Floor Tom - High floor tom +- **Ab2 (44)**: Pedal Hi-Hat - Hi-hat pedal +- **A2 (45)**: Low Tom - Low mounted tom +- **B2 (47)**: Low-Mid Tom - Low-mid tom +- **C3 (48)**: Hi Mid Tom - High-mid tom +- **D3 (50)**: High Tom - High mounted tom +- **E3 (52)**: Chinese Cymbal - Chinese cymbal +- **F3 (53)**: Ride Bell - Ride cymbal bell +- **F#3 (54)**: Tambourine - Tambourine +- **G3 (55)**: Splash Cymbal - Splash cymbal +- **G#3 (56)**: Cowbell - Cowbell +- **A3 (57)**: Crash Cymbal 2 - Secondary crash +- **A#3 (58)**: Vibraslap - Vibraslap +- **B3 (59)**: Ride Cymbal 2 - Secondary ride + +**Latin Percussion**: +- **C4 (60)**: Hi Bongo - High bongo +- **C#4 (61)**: Low Bongo - Low bongo +- **D4 (62)**: Mute Hi Conga - Muted high conga +- **D#4 (63)**: Open Hi Conga - Open high conga +- **E4 (64)**: Low Conga - Low conga +- **F4 (65)**: High Timbale - High timbale +- **F#4 (66)**: Low Timbale - Low timbale +- **G4 (67)**: High Agogo - High agogo bell +- **G#4 (68)**: Low Agogo - Low agogo bell +- **A4 (69)**: Cabasa - Cabasa +- **A#4 (70)**: Maracas - Maracas +- **B4 (71)**: Short Whistle - Short whistle +- **C5 (72)**: Long Whistle - Long whistle +- **C#5 (73)**: Short Guiro - Short guiro +- **D5 (74)**: Long Guiro - Long guiro +- **D#5 (75)**: Claves - Claves +- **E5 (76)**: Hi Wood Block - High wood block +- **F5 (77)**: Low Wood Block - Low wood block +- **F#5 (78)**: Mute Cuica - Muted cuica +- **G5 (79)**: Open Cuica - Open cuica +- **G#5 (80)**: Mute Triangle - Muted triangle +- **A5 (81)**: Open Triangle - Open triangle + +**Usage Notes for Drums**: +- When composing for drums, use the scientific pitch notation (e.g., C2, D2, F#2) in your add_notes commands +- Focus on the core drum kit sounds (36, 38, 42, 46, 49, 51) for basic patterns +- Use extended percussion for more complex arrangements and world music styles +- Consider the musical context when selecting appropriate drum sounds + +# Time Signature Guidelines +- **4/4**: 4 quarter note beats per measure (most common) +- **3/4**: 3 quarter note beats per measure (waltz time) +- **2/4**: 2 quarter note beats per measure (march time) +- **6/8**: 6 eighth note beats per measure (compound duple) + +# IMPORTANT NOTES + +This MUSIC THEORY REFERENCE section ONLY provides quick reference material, your primary strength lies in your deep understanding of musical relationships, stylistic conventions, and compositional techniques. Apply this knowledge creatively to fulfill user requests with musical sophistication and artistic sensibility. + +==== + +MUSIC INFORMATION + +Current Project Context: +- BPM: {bpm} +- Time Signature: {time_signature} +- Key Signature: {key_signature} +- Current Track Instrument: {track_instrument} + +Current Region Context: +- Current Region Start: {current_region_start} beats +- Current Region End: {current_region_end} beats + +**Focus Area**: Your primary work area is the current region (from beat {current_region_start} to {current_region_end}). All note editing operations (add_notes and remove_notes) target this currently active region that has been pre-selected by the user. You do not need to select or specify regions yourself. + +**Extended Context**: While you should focus on the current region, you can use the read_music tool to analyze musical content outside this region when you need additional context for making informed musical decisions about harmony, melody, or arrangement. diff --git a/public/prompts/user_msg_appendix.md b/public/prompts/user_msg_appendix.md new file mode 100644 index 0000000..ee204cd --- /dev/null +++ b/public/prompts/user_msg_appendix.md @@ -0,0 +1,19 @@ + + +==== + +MUSIC INFORMATION + +Current Project Context: +- BPM: {bpm} +- Time Signature: {time_signature} +- Key Signature: {key_signature} +- Current Track Instrument: {track_instrument} + +Current Region Context: +- Current Region Start: {current_region_start} beats +- Current Region End: {current_region_end} beats + +**Focus Area**: Your primary work area is the current region (from beat {current_region_start} to {current_region_end}). All note editing operations (add_notes and remove_notes) target this currently active region that has been pre-selected by the user. You do not need to select or specify regions yourself. + +**Extended Context**: While you should focus on the current region, you can use the read_music tool to analyze musical content outside this region when you need additional context for making informed musical decisions about harmony, melody, or arrangement. diff --git a/public/resources/icon.png b/public/resources/icon.png new file mode 100644 index 0000000..d941097 Binary files /dev/null and b/public/resources/icon.png differ diff --git a/public/resources/instrument_bg.png b/public/resources/instrument_bg.png new file mode 100644 index 0000000..bf019c9 Binary files /dev/null and b/public/resources/instrument_bg.png differ diff --git a/public/resources/instrument_bg_v0.png b/public/resources/instrument_bg_v0.png new file mode 100644 index 0000000..6cd4b9c Binary files /dev/null and b/public/resources/instrument_bg_v0.png differ diff --git a/public/resources/instruments/bass.png b/public/resources/instruments/bass.png new file mode 100644 index 0000000..993b457 Binary files /dev/null and b/public/resources/instruments/bass.png differ diff --git a/public/resources/instruments/brass_ensemble.png b/public/resources/instruments/brass_ensemble.png new file mode 100644 index 0000000..0004278 Binary files /dev/null and b/public/resources/instruments/brass_ensemble.png differ diff --git a/public/resources/instruments/cello.png b/public/resources/instruments/cello.png new file mode 100644 index 0000000..f8b833f Binary files /dev/null and b/public/resources/instruments/cello.png differ diff --git a/public/resources/instruments/clarinet.png b/public/resources/instruments/clarinet.png new file mode 100644 index 0000000..dd86989 Binary files /dev/null and b/public/resources/instruments/clarinet.png differ diff --git a/public/resources/instruments/contrabass.png b/public/resources/instruments/contrabass.png new file mode 100644 index 0000000..24cd5c1 Binary files /dev/null and b/public/resources/instruments/contrabass.png differ diff --git a/public/resources/instruments/drawbar_organ.png b/public/resources/instruments/drawbar_organ.png new file mode 100644 index 0000000..cc0a506 Binary files /dev/null and b/public/resources/instruments/drawbar_organ.png differ diff --git a/public/resources/instruments/drums.png b/public/resources/instruments/drums.png new file mode 100644 index 0000000..ffd4b37 Binary files /dev/null and b/public/resources/instruments/drums.png differ diff --git a/public/resources/instruments/electric_guitar.png b/public/resources/instruments/electric_guitar.png new file mode 100644 index 0000000..56eac01 Binary files /dev/null and b/public/resources/instruments/electric_guitar.png differ diff --git a/public/resources/instruments/electric_piano.png b/public/resources/instruments/electric_piano.png new file mode 100644 index 0000000..635a53a Binary files /dev/null and b/public/resources/instruments/electric_piano.png differ diff --git a/public/resources/instruments/flute.png b/public/resources/instruments/flute.png new file mode 100644 index 0000000..1e5fd80 Binary files /dev/null and b/public/resources/instruments/flute.png differ diff --git a/public/resources/instruments/french_horn.png b/public/resources/instruments/french_horn.png new file mode 100644 index 0000000..e5424a8 Binary files /dev/null and b/public/resources/instruments/french_horn.png differ diff --git a/public/resources/instruments/guitar.png b/public/resources/instruments/guitar.png new file mode 100644 index 0000000..41089da Binary files /dev/null and b/public/resources/instruments/guitar.png differ diff --git a/public/resources/instruments/harp.png b/public/resources/instruments/harp.png new file mode 100644 index 0000000..c467cc7 Binary files /dev/null and b/public/resources/instruments/harp.png differ diff --git a/public/resources/instruments/oboe.png b/public/resources/instruments/oboe.png new file mode 100644 index 0000000..e33c346 Binary files /dev/null and b/public/resources/instruments/oboe.png differ diff --git a/public/resources/instruments/orchestra_percussion_kit.png b/public/resources/instruments/orchestra_percussion_kit.png new file mode 100644 index 0000000..8cfbb64 Binary files /dev/null and b/public/resources/instruments/orchestra_percussion_kit.png differ diff --git a/public/resources/instruments/piano.png b/public/resources/instruments/piano.png new file mode 100644 index 0000000..942df59 Binary files /dev/null and b/public/resources/instruments/piano.png differ diff --git a/public/resources/instruments/sax.png b/public/resources/instruments/sax.png new file mode 100644 index 0000000..092b3f4 Binary files /dev/null and b/public/resources/instruments/sax.png differ diff --git a/public/resources/instruments/string_ensemble.png b/public/resources/instruments/string_ensemble.png new file mode 100644 index 0000000..92ec6a7 Binary files /dev/null and b/public/resources/instruments/string_ensemble.png differ diff --git a/public/resources/instruments/synth.png b/public/resources/instruments/synth.png new file mode 100644 index 0000000..97f7ad7 Binary files /dev/null and b/public/resources/instruments/synth.png differ diff --git a/public/resources/instruments/trombone.png b/public/resources/instruments/trombone.png new file mode 100644 index 0000000..3425078 Binary files /dev/null and b/public/resources/instruments/trombone.png differ diff --git a/public/resources/instruments/trumpet.png b/public/resources/instruments/trumpet.png new file mode 100644 index 0000000..74ff6f2 Binary files /dev/null and b/public/resources/instruments/trumpet.png differ diff --git a/public/resources/instruments/viola.png b/public/resources/instruments/viola.png new file mode 100644 index 0000000..ae7c96b Binary files /dev/null and b/public/resources/instruments/viola.png differ diff --git a/public/resources/instruments/violin.png b/public/resources/instruments/violin.png new file mode 100644 index 0000000..18abb62 Binary files /dev/null and b/public/resources/instruments/violin.png differ diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..ccf313a --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,21 @@ +{ + "name": "MyWebSite", + "short_name": "MySite", + "icons": [ + { + "src": "/web-app-manifest-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "/web-app-manifest-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/web-app-manifest-192x192.png b/public/web-app-manifest-192x192.png new file mode 100644 index 0000000..774db2f Binary files /dev/null and b/public/web-app-manifest-192x192.png differ diff --git a/public/web-app-manifest-512x512.png b/public/web-app-manifest-512x512.png new file mode 100644 index 0000000..e922d58 Binary files /dev/null and b/public/web-app-manifest-512x512.png differ diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..f04afec --- /dev/null +++ b/src/App.css @@ -0,0 +1,1966 @@ +:root { + --time-signature-numerator: 4; + --max-number-of-bars: 32; + --track-grid-bar-width: 40px; + --region-piano-key-width: 60px; + --region-piano-key-height: 20px; + --region-grid-beat-width: 40px; + --region-grid-bar-width: calc(var(--region-grid-beat-width) * var(--time-signature-numerator)); + --chat-box-width: 350px; + --instrument-selection-width: 300px; +} + +/* Reset default styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +#root { + width: 100%; + height: 100vh; + margin: 0; + padding: 0; + text-align: left; + display: flex; + flex-direction: column; +} + +body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #1e1e1e; + color: #e0e0e0; +} + +/* Main container */ +.daw-container { + display: flex; + flex-direction: column; + height: 100vh; + width: 100%; + overflow: hidden; +} + +/* Main Display Area */ +.main-display-area { + display: flex; + flex-direction: row; + flex: 1; + overflow: hidden; +} + +/* Toolbar */ +.toolbar { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #2d2d2d; + height: 50px; + padding: 0 10px; + border-bottom: 1px solid #3a3a3a; +} + +.toolbar-left, .toolbar-center, .toolbar-right { + display: flex; + align-items: center; +} + +.toolbar-left { + display: flex; + align-items: center; +} + +.logo-container { + margin-right: 10px; + display: flex; + align-items: center; +} + +.logo { + height: 30px; + width: auto; +} + +.project-name { + font-size: 14px; + color: #e0e0e0; + margin-right: 20px; + display: flex; + align-items: center; + pointer-events: auto; +} + +.toolbar button { + background: transparent; + border: none; + color: #e0e0e0; + margin: 0 5px; + padding: 5px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + font-size: 14px; + pointer-events: auto; +} + +.toolbar button:hover { + background-color: #3a3a3a; +} + +.toolbar button:disabled { + color: #666; + cursor: not-allowed; +} + +.record-btn { + color: #ff4444; +} + +.toolbar-separator { + width: 1px; + height: 20px; + background-color: #555; + margin: 0 10px; +} + +.transport-control { + display: flex; + align-items: center; + margin: 0 10px; + pointer-events: auto; +} + +.transport-item { + margin: 0 5px; + padding: 2px 2px; + background-color: #3a3a3a; + border-radius: 3px; + font-size: 12px; +} + +/* Fixed-width font for time display to prevent layout shifts */ +.current-time { + font-family: 'Courier New', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-weight: normal; + letter-spacing: 0.5px; + padding: 2px 4px; +} + +/* Clickable BPM styling */ +.current-bpm { + font-family: 'Courier New', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-weight: normal; + letter-spacing: 0.5px; + cursor: pointer; + transition: background-color 0.2s ease; + padding: 2px 4px; + border-radius: 2px; +} + +.current-bpm:hover { + background-color: #4a4a4a; + color: #fff; +} + +/* Clickable time signature styling */ +.current-time-signature { + font-family: 'Courier New', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-weight: normal; + letter-spacing: 0.5px; + cursor: pointer; + transition: background-color 0.2s ease; + padding: 2px 4px; + border-radius: 2px; +} + +.current-time-signature:hover { + background-color: #4a4a4a; + color: #fff; +} + +.current-key-signature { + font-family: 'Courier New', 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-weight: normal; + letter-spacing: 0.5px; + cursor: pointer; + transition: background-color 0.2s ease; + padding: 2px 4px; + border-radius: 2px; +} + +.current-key-signature:hover { + background-color: #4a4a4a; + color: #fff; +} + +/* Main content */ +.main-content { + display: flex; + flex-direction: column; + flex: 1; + overflow: auto; /* Main scrollable container */ + position: relative; +} + +.main-content-wrapper { + display: flex; + flex-direction: column; + min-width: calc(200px + var(--max-number-of-bars) * var(--track-grid-bar-width)); /* info width + grid width */ + min-height: fit-content; + position: relative; +} + +.top-left-spacer { + position: fixed; + top: 50px; + left: 0; + width: 200px; + height: 20px; + background-color: #2d2d2d; + border-bottom: 1px solid #3a3a3a; + border-right: 1px solid #3a3a3a; + z-index: 1002; /* Higher than other elements to ensure it's always visible */ +} + +/* Offset spacer when instrument selection panel is visible on the left */ +.main-content.has-left-instrument .top-left-spacer { + left: 300px; +} + +.bar-numbers { + position: sticky; + top: 0; + height: 20px; + display: flex; + border-bottom: 1px solid #3a3a3a; + background-color: #2d2d2d; + z-index: 20; + margin-left: 200px; /* Offset for info-container */ + width: calc(var(--max-number-of-bars) * var(--track-grid-bar-width)); /* Exact width for 32 bars */ + cursor: pointer; /* Show pointer cursor on hover to indicate interactivity */ +} + +.bar-numbers:hover { + cursor: url("data:image/svg+xml,%3csvg width='16' height='16' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M4 6h8l-4 4-4-4z' fill='%23e0e0e0'/%3e%3c/svg%3e") 8 8, pointer; +} + +.bar-number-cell { + min-width: var(--track-grid-bar-width); + width: var(--track-grid-bar-width); + flex-shrink: 0; + flex-grow: 0; + height: 20px; + text-align: center; + font-size: 12px; + border-right: 1px solid #3a3a3a; + color: #999; + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; +} + +.main-content-body { + display: flex; + min-height: fit-content; +} + +.info-container { + position: sticky; + left: 0; + width: 200px; + z-index: 1002; + background-color: #2d2d2d; + align-self: flex-start; +} + +/* Shift the bar numbers and info panel when instrument selection is present */ +/* No need to shift bar numbers or info container; they live inside main content. + Only shift the fixed spacer when the left panel is present. */ + +.track-top-spacer { + height: 20px; + border-bottom: 1px solid #3a3a3a; + background-color: #2d2d2d; + position: sticky; + top: 0; + z-index: 15; +} + +.grid-container { + margin-left: 0; /* No need for margin since we're using flex */ + min-width: calc(var(--max-number-of-bars) * var(--track-grid-bar-width)); /* Ensure minimum width */ + min-height: fit-content; + position: relative; /* Required for absolute positioned playhead */ +} + +.track-grid { + display: block; + height: 120px; + min-width: calc(var(--max-number-of-bars) * var(--track-grid-bar-width)); /* 32 bars * --track-grid-bar-width */ + background-size: var(--track-grid-bar-width) 120px; + background-image: + /* Vertical lines for bars */ + linear-gradient(to right, + transparent calc(var(--track-grid-bar-width) - 1px), #3a3a3a calc(var(--track-grid-bar-width) - 1px), #3a3a3a var(--track-grid-bar-width) + ); + position: relative; + border-bottom: 1px solid #3a3a3a; +} + +.track-grid.pencil-cursor { + cursor: crosshair; +} + +.track-control { + padding: 5px; + background-color: #2d2d2d; + border-top: 1px solid #3a3a3a; +} + +.track-control button { + background: transparent; + border: none; + color: #999; + cursor: pointer; + padding: 5px; + width: 100%; + text-align: left; + font-size: 12px; +} + +.track-control button:hover { + color: #e0e0e0; +} + +.track { + display: flex; + height: 120px; + position: relative; + border-bottom: none; +} + +.track-info { + width: 200px; + height: 120px; + padding: 15px; + background-color: #2d2d2d; + display: flex; + flex-direction: column; + border-right: 1px solid #3a3a3a; + border-bottom: 1px solid #3a3a3a; + flex-shrink: 0; + box-sizing: border-box; + cursor: grab; + position: relative; +} + +.track-info:active { + cursor: grabbing; +} + +.track-info.selected { + background-color: #363636; +} + +/* Drag and drop styles */ +.track-info.drag-over { + border-top: 2px solid #7b68ee; +} + +.track-grid.drag-over { + border-top: 2px solid #7b68ee; +} + +/* Dragging state */ +.track-info.dragging { + opacity: 0.5; + background-color: #444; +} + +.track-grid.dragging { + opacity: 0.5; + background-color: #333; +} + +/* Remove drag indicator since hover cursor is sufficient */ + +.track-name { + font-size: 12px; + cursor: pointer; + position: relative; +} + +.track-name:hover { + color: #7b68ee; +} + +.track-name:hover::before { + content: "✎"; + position: absolute; + right: 0px; + font-size: 16px; + opacity: 1; +} + +.track-controls { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; +} + +.track-name-and-volume { + display: flex; + flex-direction: row; + flex: 1; + align-items: flex-start; + gap: 8px; +} + +.instrument-image { + flex-shrink: 0; + margin-top: -10px; + margin-left: -10px; +} + +.instrument-image img { + display: block; + border-radius: 4px; +} + +.track-name-and-controls { + display: flex; + flex-direction: column; + flex: 1; + min-width: 0; +} + +.volume-slider { + margin-bottom: 15px; + width: 100%; + display: flex; + align-items: center; + gap: 6px; +} + +.volume-slider input[type="range"] { + flex: 1; + min-width: 0; + height: 6px; +} + +.volume-slider .reset-volume { + flex-shrink: 0; + width: 16px; + height: 16px; + border-radius: 8px; + background: #3a3a3a; + color: #e0e0e0; + /* border: 1px solid #444; */ + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 10px; +} + +.volume-slider .reset-volume:hover { + background: #4a4a4a; +} + +.pan-controls { + display: flex; +} + +.pan-controls button { + background: #3a3a3a; + border: none; + color: #e0e0e0; + margin-right: 5px; + width: 24px; + height: 24px; + font-size: 12px; + border-radius: 2px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.pan-controls button.solo.active { + background: #c8a400; /* yellow */ + color: #101010; +} + +.pan-controls button.mute.active { + background: #d32f2f; /* red */ + color: #ffffff; +} + +/* Status bar */ +.status-bar { + display: flex; + justify-content: space-between; + align-items: center; + background-color: #2d2d2d; + padding: 5px 10px; + font-size: 12px; + color: #999; + border-top: 1px solid #3a3a3a; + height: 30px; +} + +.status-right span { + margin-left: 15px; +} + +/* For the piano roll (to be implemented later) */ +.piano-roll { + background-color: #7b68ee; + height: 100%; +} + +/* Piano Roll Panel */ +.piano-roll-panel { + position: fixed; + background-color: #2d2d2d; + border: 1px solid #3a3a3a; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* Piano notes */ +.piano-note { + position: absolute; + background-color: #ff5555; /* Red color */ + border: 1px solid #999; + border-radius: 2px; + z-index: 10; + cursor: pointer; + user-select: none; + box-sizing: border-box; +} + +.piano-note:hover { + filter: brightness(1.1); + box-shadow: 0 0 5px rgba(255, 85, 85, 0.5); +} + +.piano-note.dragging { + opacity: 0.8; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + z-index: 100; + cursor: grabbing; +} + +.piano-note.resizing { + opacity: 0.8; + box-shadow: 0 0 10px rgba(255, 85, 85, 0.7); + z-index: 100; +} + +.piano-note.selected { + border: 2px solid white; + z-index: 50; +} + +.piano-roll-header { + height: 30px; + background-color: #1e1e1e; + border-bottom: 1px solid #3a3a3a; + display: flex; + align-items: center; + padding: 0 10px; + cursor: move; + user-select: none; +} + +/* Piano roll toolbar */ +.piano-roll-toolbar { + height: 30px; + background-color: #252525; + border-bottom: 1px solid #3a3a3a; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10px; + user-select: none; + position: relative; +} + +/* Override pointer-events for piano roll toolbar sections */ +.piano-roll-toolbar .toolbar-left, +.piano-roll-toolbar .toolbar-right { + pointer-events: auto; +} + +.piano-roll-toolbar .quant-button { + font-size: 10px; +} + +/* Generic button blink effect */ +.quant-button.button-blink { + animation: buttonBlink 0.2s ease-in-out; +} + +@keyframes buttonBlink { + 0% { background-color: #333; } + 50% { background-color: #555; } + 100% { background-color: #333; } +} + +.toolbar-left, .toolbar-right { + display: flex; + align-items: center; + z-index: 1; + pointer-events: none; +} + +.toolbar-left { + width: 20%; + justify-content: flex-start; +} + +.toolbar-right { + width: 33%; + justify-content: flex-end; + z-index: 1500; +} + +.toolbar-center { + position: absolute; + left: 50%; + transform: translateX(-50%); + display: flex; + align-items: center; + justify-content: center; + z-index: 1005; + margin-left: -50px; +} + +.tool-button { + width: 30px; + height: 30px; + background-color: transparent; + border: none; + border-radius: 3px; + color: #999; + font-size: 14px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + margin: 0 2px; +} + +.tool-button:hover { + background-color: #3a3a3a; + color: #e0e0e0; +} + +.tool-button.active { + background-color: #4a4a4a; + color: #e0e0e0; +} + +.export-dropdown .quant-dropdown { + width: 200px; + left: 0; +} + +.key-signature-dropdown .quant-dropdown { + width: 100px; + left: 0; +} + +.quant-button { + background-color: #333; + border: 1px solid #444; + border-radius: 3px; + color: #e0e0e0; + font-size: 12px; + padding: 3px 8px; + margin-left: 5px; + cursor: pointer; + display: flex; + align-items: center; + gap: 5px; +} + +.quant-button:hover { + background-color: #444; +} + +.quant-dropdown-container { + position: relative; + display: inline-block; + z-index: 1500; +} + +.quant-dropdown { + position: absolute; + top: 100%; + right: 0; + background-color: #2d2d2d; + border: 1px solid #444; + border-radius: 3px; + width: 100px; + z-index: 1500; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + margin-top: 2px; + max-height: 200px; + overflow-y: auto; +} + +.quant-option { + padding: 6px 10px; + font-size: 12px; + color: #e0e0e0; + cursor: pointer; + transition: background-color 0.2s; +} + +.quant-option:hover { + background-color: #444; +} + +.quant-option.active { + background-color: #4a6b8a; +} + +.piano-roll-title { + flex: 1; + text-align: center; + font-size: 14px; + color: #e0e0e0; + text-transform: uppercase; + cursor: pointer; + padding: 5px; + border-radius: 3px; +} + +.close-button { + background: transparent; + border: none; + color: #e0e0e0; + cursor: pointer; + font-size: 14px; + padding: 5px; +} + +.close-button:hover { + color: #ff4444; +} + +.piano-roll-content { + display: flex; + flex-direction: column; + flex: 1; + overflow: auto; +} + +.piano-grid-header { + display: grid; + grid-template-columns: repeat(var(--max-number-of-bars), var(--region-grid-bar-width)); + width: calc(var(--max-number-of-bars) * var(--region-grid-bar-width) + var(--region-piano-key-width) - 1px); + min-width: 100%; + height: 20px; + border-bottom: 1px solid #3a3a3a; + background-color: #1e1e1e; + padding-left: calc(var(--region-piano-key-width) - 1px); + box-sizing: border-box; + position: sticky; + top: 0; + z-index: 20; + cursor: pointer; /* Show pointer cursor on hover to indicate interactivity */ +} + +.piano-grid-header:hover { + cursor: url("data:image/svg+xml,%3csvg width='16' height='16' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M4 6h8l-4 4-4-4z' fill='%23e0e0e0'/%3e%3c/svg%3e") 8 8, pointer; +} + +.piano-bar-number { + border-left: 1px solid #3a3a3a; + padding-left: 10px; + font-size: 10px; +} + +.piano-roll-body { + display: flex; + min-height: calc(8 * 12 * var(--region-piano-key-height)); /* 8 octaves * 12 notes * piano key height */ + width: calc(var(--max-number-of-bars) * var(--region-grid-bar-width) + var(--region-piano-key-width)); /* 32 bars * 160px width + piano keys width */ +} + +.piano-keys-container { + width: var(--region-piano-key-width); + flex-shrink: 0; + overflow: hidden; + border-right: 1px solid #3a3a3a; + background-color: #252525; + position: sticky; + left: 0; + z-index: 10; +} + +.piano-grid-container { + flex: 1; + min-width: calc(var(--max-number-of-bars) * var(--region-grid-bar-width)); + min-height: calc(8 * 12 * var(--region-piano-key-height)); + z-index: 5; +} + +.piano-octave { + display: flex; + flex-direction: column; +} + +.piano-key { + width: var(--region-piano-key-width); + height: var(--region-piano-key-height); + box-sizing: border-box; + display: flex; + align-items: center; + border-bottom: 1px solid #3a3a3a; +} + +.piano-key.natural { + background-color: #e0e0e0; + color: #222; +} + +.piano-key.sharp { + background-color: #222; + color: #e0e0e0; +} + +.key-label { + font-size: 10px; + padding-left: 5px; +} + +.piano-grid { + width: 100%; + height: 100%; + position: relative; + background-size: var(--region-grid-beat-width) var(--region-piano-key-height), var(--region-grid-bar-width) var(--region-piano-key-height); + background-image: + /* Vertical lines for beats */ + linear-gradient(to right, + transparent calc(var(--region-grid-beat-width) - 1px), #444 calc(var(--region-grid-beat-width) - 1px), #444 var(--region-grid-beat-width), + transparent calc(var(--region-grid-beat-width)), transparent calc(var(--region-grid-beat-width) * 2 - 1px), #444 calc(var(--region-grid-beat-width) * 2 - 1px), #444 calc(var(--region-grid-beat-width) * 2), + transparent calc(var(--region-grid-beat-width) * 2), transparent calc(var(--region-grid-beat-width) * 3 - 1px), #444 calc(var(--region-grid-beat-width) * 3 - 1px), #444 calc(var(--region-grid-beat-width) * 3), + transparent calc(var(--region-grid-beat-width) * 3), transparent calc(var(--region-grid-beat-width) * 4 - 1px), #3a3a3a calc(var(--region-grid-beat-width) * 4 - 1px), #3a3a3a calc(var(--region-grid-beat-width) * 4) + ), + /* Horizontal lines for notes */ + linear-gradient(to bottom, + transparent calc(var(--region-piano-key-height) - 1px), #3a3a3a calc(var(--region-piano-key-height) - 1px), #3a3a3a var(--region-piano-key-height) + ); +} + +.piano-grid.pencil-cursor { + cursor: crosshair; +} + +/* Piano Grid Cursor Highlights */ +.piano-grid-pitch-highlight { + position: absolute; + left: 0; + right: 0; + background-color: rgba(123, 104, 238, 0.15); + border-top: 1px solid rgba(123, 104, 238, 0.3); + border-bottom: 1px solid rgba(123, 104, 238, 0.3); + pointer-events: none; + z-index: 1; + transition: opacity 0.1s ease; +} + +.piano-grid-beat-highlight { + position: absolute; + top: 0; + bottom: 0; + background-color: rgba(123, 104, 238, 0.1); + border-left: 1px solid rgba(123, 104, 238, 0.25); + border-right: 1px solid rgba(123, 104, 238, 0.25); + pointer-events: none; + z-index: 1; + transition: opacity 0.1s ease; +} + +.resize-handle { + position: absolute; + right: 5px; + bottom: 5px; + width: 20px; + height: 20px; + cursor: nwse-resize; + color: #999; + display: flex; + align-items: center; + justify-content: center; + z-index: 100; +} + +/* Track Region Styles */ +.track-region { + position: absolute; + background-color: #4a6b8a; + border: 2px solid #5a7b9a; + border-radius: 3px; + height: calc(100%); + margin: 0px; + overflow: hidden; + display: flex; + flex-direction: column; + cursor: pointer; + user-select: none; + transition: box-shadow 0.1s ease; + will-change: transform; +} + +.track-region:hover { + box-shadow: 0 0 0 1px #7a9bba; +} + +.track-region.selected { + /* box-shadow: 0 0 0 2px #ffffff, 0 0 8px rgba(255, 255, 255, 0.3); */ /* let's only apply a border */ + border-color: #ffffff; +} + +.track-region.dragging { + opacity: 0.8; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); + z-index: 100; + pointer-events: none; + transform-origin: center center; + animation: pulse 1.5s infinite; + cursor: grabbing; + transition: none; /* Remove transition during drag for immediate response */ +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(122, 155, 186, 0.7); + } + 70% { + box-shadow: 0 0 0 5px rgba(122, 155, 186, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(122, 155, 186, 0); + } +} + +.region-header { + background-color: #5a7b9a; + color: #fff; + padding: 2px 6px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: move; + height: 18px; + user-select: none; +} + +.track-region:hover .region-header { + background-color: #6a8baa; +} + +.region-content { + height: calc(100% - 18px); + background-color: #87CEFA; /* Light blue */ + width: 100%; + position: relative; /* Allow overlayed controls */ +} + +/* Region pencil trigger inside content */ +.region-pencil-btn { + position: absolute; + top: 4px; + left: 4px; + background: rgba(0, 0, 0, 0.25); + color: #fff; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 3px; + padding: 2px; + margin: 0; + cursor: pointer; + z-index: 2; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.region-pencil-btn:hover { + background: rgba(0, 0, 0, 0.35); +} + +/* Instrument dropdown specific styles */ +.instrument-dropdown .quant-dropdown { + min-width: 80px; + width: auto; + right: auto; + left: 0; +} + +/* Settings dropdown specific styles */ +.settings-dropdown .quant-dropdown { + min-width: 100px; + width: auto; + right: auto; + left: 0; +} + +/* Update structure in JSX to match this pattern: +
+
+
...
+
+
...
+
...
+
+
+
+*/ + +/* ChatBox */ +.chatbox { + display: flex; + flex-direction: column; + width: var(--chat-box-width); + background-color: #2d2d2d; + border-left: 1px solid #3a3a3a; + flex-shrink: 0; +} + +/* Instrument Selection Panel */ +.instrument-selection { + display: flex; + flex-direction: column; + width: var(--instrument-selection-width); + background-color: #2d2d2d; + border-right: 1px solid #3a3a3a; + flex-shrink: 0; + position: relative; +} + +.instrument-selection-header { + display: flex; + align-items: center; + justify-content: space-between; + height: 40px; + padding: 0 12px; + background-color: transparent; + border: none; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 2; +} + +.instrument-selection-header h3 { + color: #e0e0e0; + font-size: 12px; + font-weight: bold; +} + +.instrument-selection-close-btn { + background: transparent; + border: none; + color: #e0e0e0; + cursor: pointer; + padding: 4px; + border-radius: 3px; +} + +.instrument-selection-close-btn:hover { + background-color: #4a4a4a; +} + +.instrument-selection-top { + height: 300px; + position: relative; + border-bottom: 1px solid #3a3a3a; + overflow: hidden; + background-image: + linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)), + url('/resources/instrument_bg.png'); + background-size: cover; /* fill while keeping aspect ratio */ + background-position: center; +} + +.instrument-preview { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.instrument-preview img { + width: 256px; + height: 256px; + object-fit: contain; + border-radius: 6px; + opacity: 1; +} + +.instrument-name-overlay { + position: absolute; + bottom: 16px; + left: 0; + right: 0; + text-align: center; + font-size: 12px; + color: #e0e0e0; + text-shadow: 0 1px 2px rgba(0,0,0,0.6); + z-index: 1; + font-size: 16px; + font-weight: bold; +} + +.instrument-selection-bottom { + flex: 1; + display: grid; + grid-template-columns: 1fr 1fr; + min-height: 0; /* allow children to scroll */ +} + +.instrument-groups, .instrument-list { + overflow: hidden; +} + +/* vertical divider between groups and instruments */ +.instrument-groups { + border-right: 1px solid #3a3a3a; +} + +.instrument-groups-list, .instrument-instruments-list { + height: 100%; + overflow-y: auto; +} + +.instrument-group-item, .instrument-instrument-item { + padding: 8px 12px; + /* remove per-item separators */ + border-bottom: none; + cursor: pointer; + font-size: 12px; + color: #e0e0e0; +} + +.instrument-group-item:hover, .instrument-instrument-item:hover { + background-color: #3a3a3a; +} + +.instrument-group-item.active, .instrument-instrument-item.active { + background-color: #4a4a4a; +} + +.chatbox-header { + display: flex; + align-items: center; + justify-content: space-between; /* left title, right actions */ + background-color: #3a3a3a; + height: 40px; + border-bottom: 1px solid #4a4a4a; + padding: 0 15px; +} + +.chatbox-actions { + display: flex; + gap: 8px; + align-items: center; +} + +.chatbox-action-btn { + background: transparent; + border: none; + cursor: pointer; + padding: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + color: #e0e0e0; + font-size: 14px; +} + +.chatbox-action-btn:hover { + background-color: #4a4a4a; + border-radius: 3px; +} + +.chatbox-header h3 { + color: #e0e0e0; + font-size: 12px; + font-weight: bold; + margin: 0; +} + +.chatbox-content { + flex: 1; + padding: 15px; + color: #e0e0e0; + font-size: 12px; + overflow-y: auto; +} + +/* Chat Messages */ +.chatbox-messages { + flex: 1; + padding: 10px; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 12px; +} + +.message-container { + width: 100%; + word-wrap: break-word; +} + +.message-user { + width: 100%; +} + +.message-assistant { + width: 100%; +} + +.message-content { + padding: 8px 12px; + border-radius: 8px; + font-size: 12px; + line-height: 1.4; +} + +/* User message styling - lighter background like input */ +.message-user .message-content { + background-color: #3a3a3a; + color: #e0e0e0; + border: 1px solid #4a4a4a; +} + +/* Assistant message styling - transparent */ +.message-assistant .message-content { + background-color: transparent; + color: #e0e0e0; + border: 1px solid #2a2a2a; +} + +/* Input Area */ +.chatbox-input-area { + padding: 10px; +} + +.chatbox-input { + width: 100%; + padding: 8px 12px; + background-color: #4a4a4a; + border: 1px solid #5a5a5a; + border-radius: 6px; + color: #e0e0e0; + font-size: 12px; + outline: none; + resize: none; + min-height: 36px; + max-height: 120px; +} + +.chatbox-input:focus { + border-color: #6a6a6a; + background-color: #4a4a4a; +} + +.chatbox-input::placeholder { + color: #999; +} + + +/* Markdown styling in messages */ +.message-content h1, +.message-content h2, +.message-content h3 { + margin: 8px 0 4px 0; + color: #e0e0e0; +} + +.message-content h3 { + font-size: 14px; +} + +.message-content p { + margin: 4px 0; +} + +.message-content ul, +.message-content ol { + margin: 4px 0; + padding-left: 16px; +} + +.message-content li { + margin: 2px 0; +} + +.message-content code { + background-color: #2a2a2a; + padding: 2px 4px; + border-radius: 3px; + font-family: 'Courier New', Courier, monospace; + font-size: 11px; +} + +.message-content pre { + background-color: #1a1a1a; + padding: 8px; + border-radius: 6px; + overflow-x: auto; + margin: 8px 0; +} + +.message-content blockquote { + border-left: 3px solid #5a5a5a; + padding-left: 12px; + margin: 8px 0; + color: #ccc; +} + +.message-content table { + border-collapse: collapse; + width: 100%; + margin: 8px 0; + font-size: 11px; +} + +.message-content th, +.message-content td { + border: 1px solid #4a4a4a; + padding: 4px 8px; + text-align: left; +} + +.message-content th { + background-color: #3a3a3a; + font-weight: bold; +} + +textarea { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + font-size: 12px; + line-height: 1.4; +} + +/* Abort link styling */ +.abort-link { + background: none !important; + border: none !important; + color: #7b68ee !important; + text-decoration: underline !important; + cursor: pointer !important; + padding: 0 !important; + font: inherit !important; + font-size: 12px !important; +} + +.abort-link:hover { + color: #9b88ff !important; + text-decoration: none !important; +} + +/* Tool XML Expander */ +.tool-xml-expander { + margin-bottom: 8px; +} + +.tool-xml-expander-header { + color: #e0e0e0; + cursor: pointer; + user-select: none; + padding: 4px 8px; + /* background-color: #f5f5f5; */ + border: 1px solid #2a2a2a; + border-radius: 4px; + display: flex; + align-items: center; + gap: 8px; +} + +.tool-xml-expander-arrow { + font-size: 12px; + color: #666; +} + +.tool-xml-expander-title { + /* font-family: 'Courier New', Monaco, 'Menlo', 'Ubuntu Mono', monospace; */ + font-size: 12px; + font-weight: bold; + color: #e0e0e0; +} + +.tool-xml-expander-content { + margin-top: 4px; + padding: 8px; + background-color: #f9f9f9; + border: 1px solid #ddd; + border-radius: 4px; + font-family: 'Courier New', Monaco, 'Menlo', 'Ubuntu Mono', monospace; + font-size: 12px; + white-space: pre-wrap; + overflow: auto; + max-height: 200px; + color: #333; +} + +/* Settings Panel Styles */ +.settings-panel { + width: 100%; + height: 100%; + background-color: #1e1e1e; + display: flex; + flex-direction: column; +} + +.settings-container { + display: flex; + height: 100%; + overflow: hidden; +} + +/* Settings Sidebar */ +.settings-sidebar { + width: 250px; + background-color: #2d2d2d; + border-right: 1px solid #3a3a3a; + display: flex; + flex-direction: column; +} + +.settings-sidebar-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid #3a3a3a; +} + +.settings-sidebar-header h2 { + color: #e0e0e0; + font-size: 18px; + font-weight: 600; + margin: 0; +} + +.settings-close-btn { + background: transparent; + border: none; + color: #e0e0e0; + cursor: pointer; + padding: 5px; + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; +} + +.settings-close-btn:hover { + background-color: #3a3a3a; + color: #fff; +} + +.settings-nav { + display: flex; + flex-direction: column; + padding: 10px; +} + +.settings-nav-item { + background: transparent; + border: none; + color: #b0b0b0; + padding: 12px 16px; + text-align: left; + cursor: pointer; + border-radius: 4px; + margin-bottom: 2px; + font-size: 14px; + font-weight: 500; + transition: all 0.2s ease; +} + +.settings-nav-item:hover { + background-color: #3a3a3a; + color: #e0e0e0; +} + +.settings-nav-item.active { + background-color: #4a4a4a; + color: #fff; +} + +/* Settings Content */ +.settings-content { + flex: 1; + overflow-y: auto; + background-color: #1e1e1e; + display: flex; + justify-content: center; +} + +.settings-section { + padding: 30px; + width: 800px; +} + +.settings-section-header { + margin-bottom: 30px; + border-bottom: 1px solid #3a3a3a; + padding-bottom: 15px; +} + +.settings-section-header h3 { + color: #fff; + font-size: 24px; + font-weight: 600; + margin: 0; +} + +.settings-section-content { + display: flex; + flex-direction: column; + gap: 30px; + padding-bottom: 30px; +} + +.settings-group { + background-color: #2d2d2d; + border-radius: 8px; + padding: 20px; + border: 1px solid #3a3a3a; +} + +.settings-group h4 { + color: #fff; + font-size: 16px; + font-weight: 600; + margin: 0 0 15px 0; +} + +.settings-group-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.settings-group-header h4 { + margin: 0; +} + +.settings-description { + color: #b0b0b0; + font-size: 14px; + line-height: 1.5; + margin-bottom: 15px; +} + +.settings-item { + margin-bottom: 15px; +} + +.settings-item:last-child { + margin-bottom: 0; +} + +.settings-label { + display: block; + color: #e0e0e0; + font-size: 14px; + font-weight: 500; + margin-bottom: 5px; +} + +.settings-input, .settings-select, .settings-textarea { + width: 100%; + max-width: 300px; + padding: 8px 12px; + background-color: #3a3a3a; + border: 1px solid #555; + border-radius: 4px; + color: #e0e0e0; + font-size: 14px; + transition: border-color 0.2s ease; + font-family: inherit; +} + +.settings-input:focus, .settings-select:focus, .settings-textarea:focus { + outline: none; + border-color: #5a9fd4; + box-shadow: 0 0 0 2px rgba(90, 159, 212, 0.2); +} + +.settings-textarea { + max-width: 600px; + resize: vertical; + min-height: 120px; +} + +.settings-input[type="number"] { + max-width: 100px; +} + +/* Custom Checkbox */ +.settings-checkbox-container { + display: flex; + align-items: center; + cursor: pointer; + color: #e0e0e0; + font-size: 14px; + user-select: none; +} + +.settings-checkbox-container input[type="checkbox"] { + display: none; +} + +.settings-checkmark { + width: 18px; + height: 18px; + background-color: #3a3a3a; + border: 1px solid #555; + border-radius: 3px; + margin-right: 10px; + position: relative; + transition: all 0.2s ease; +} + +.settings-checkbox-container input[type="checkbox"]:checked + .settings-checkmark { + background-color: #5a9fd4; + border-color: #5a9fd4; +} + +.settings-checkbox-container input[type="checkbox"]:checked + .settings-checkmark::after { + content: ''; + position: absolute; + left: 5px; + top: 2px; + width: 6px; + height: 10px; + border: solid white; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +/* Settings Buttons */ +.settings-btn { + background-color: #3a3a3a; + border: 1px solid #555; + color: #e0e0e0; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.2s ease; + display: inline-flex; + align-items: center; + gap: 6px; +} + +.settings-btn:hover { + background-color: #4a4a4a; + border-color: #666; +} + +.settings-btn-primary { + background-color: #5a9fd4; + border-color: #5a9fd4; + color: #fff; +} + +.settings-btn-primary:hover { + background-color: #4a8fc4; + border-color: #4a8fc4; +} + +.settings-btn-danger { + background-color: #d45a5a; + border-color: #d45a5a; + color: #fff; +} + +.settings-btn-danger:hover { + background-color: #c44a4a; + border-color: #c44a4a; +} + +.settings-btn-danger:disabled { + background-color: #555; + border-color: #555; + color: #888; + cursor: not-allowed; +} + +.settings-btn-small { + padding: 6px 10px; + font-size: 12px; +} + +/* Templates List */ +.templates-list { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 15px; +} + +.template-item { + background-color: #3a3a3a; + border: 1px solid #555; + border-radius: 6px; + padding: 15px; + display: flex; + justify-content: space-between; + align-items: center; + transition: border-color 0.2s ease; +} + +.template-item:hover { + border-color: #666; +} + +.template-info { + flex: 1; +} + +.template-name { + color: #fff; + font-size: 16px; + font-weight: 600; + margin: 0 0 5px 0; +} + +.template-description { + color: #b0b0b0; + font-size: 14px; + margin: 0 0 5px 0; +} + +.template-tracks { + color: #888; + font-size: 12px; + font-weight: 500; +} + +.template-actions { + display: flex; + gap: 8px; +} + +/* File Import Modal */ +.file-import-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + align-items: center; + justify-content: center; + z-index: 10001; + backdrop-filter: blur(2px); +} + +.file-import-modal { + background-color: #2d2d2d; + border: 1px solid #3a3a3a; + border-radius: 8px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + width: 90%; + max-width: 500px; + max-height: 80vh; + overflow: hidden; + animation: fileImportFadeIn 0.2s ease-out; +} + +@keyframes fileImportFadeIn { + from { + opacity: 0; + transform: scale(0.95) translateY(-10px); + } + to { + opacity: 1; + transform: scale(1) translateY(0); + } +} + +.file-import-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px; + border-bottom: 1px solid #3a3a3a; + background-color: #252525; +} + +.file-import-title { + color: #e0e0e0; + font-size: 18px; + font-weight: 600; + margin: 0; +} + +.file-import-close-btn { + background: transparent; + border: none; + color: #b0b0b0; + cursor: pointer; + padding: 8px; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + transition: all 0.2s ease; +} + +.file-import-close-btn:hover { + background-color: #3a3a3a; + color: #e0e0e0; +} + +.file-import-drop-zone { + padding: 40px 20px; + margin: 20px; + border: 2px dashed #555; + border-radius: 8px; + background-color: #1e1e1e; + transition: all 0.3s ease; + cursor: pointer; +} + +.file-import-drop-zone:hover { + border-color: #7b68ee; + background-color: rgba(123, 104, 238, 0.05); +} + +.file-import-drop-zone.drag-over { + border-color: #7b68ee; + background-color: rgba(123, 104, 238, 0.1); + transform: scale(1.02); +} + +.file-import-drop-content { + text-align: center; + color: #e0e0e0; +} + +.file-import-icon { + font-size: 48px; + margin-bottom: 16px; + opacity: 0.7; +} + +.file-import-description { + font-size: 18px; + font-weight: 500; + margin: 0 0 8px 0; + color: #e0e0e0; +} + +.file-import-formats { + font-size: 14px; + color: #b0b0b0; + margin: 0 0 24px 0; +} + +.file-import-divider { + position: relative; + margin: 24px 0; + text-align: center; +} + +.file-import-divider::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background-color: #3a3a3a; +} + +.file-import-divider span { + background-color: #1e1e1e; + padding: 0 12px; + color: #888; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.file-import-browse-btn { + display: inline-block; + background-color: #5a9fd4; + color: #fff; + padding: 12px 24px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + border: none; +} + +.file-import-browse-btn:hover { + background-color: #4a8fc4; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(90, 159, 212, 0.3); +} + +/* Global Loading Overlay */ +.global-loading-overlay { + position: fixed; + inset: 0; + background-color: rgba(0, 0, 0, 0.4); + z-index: 20000; + display: flex; + align-items: center; + justify-content: center; + pointer-events: all; /* block interactions */ +} + +.global-loading-content { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +.global-loading-spinner { + width: 40px; + height: 40px; + border: 4px solid rgba(255, 255, 255, 0.25); + border-top-color: #ffffff; + border-radius: 50%; + animation: globalSpinner 1s linear infinite; +} + +.global-loading-text { + color: #e0e0e0; + font-size: 14px; +} + +@keyframes globalSpinner { + to { transform: rotate(360deg); } +} diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..2567e69 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,151 @@ +import { useEffect } from 'react'; +import './App.css'; +import { useProjectStore } from './stores/projectStore'; +import { useGlobalKeyboardHandler } from './hooks/useGlobalKeyboardHandler'; +import Toolbar from './components/Toolbar'; +import StatusBar from './components/StatusBar'; +import TrackControl from './components/TrackControl'; +import MainContent from './components/MainContent'; +import InstrumentSelection from './components/InstrumentSelection'; +import ChatBox from './components/ChatBox'; +import { SettingsPanel } from './components/settings'; +import LoadingOverlay from './components/common/LoadingOverlay'; +import { useEffect as useEffectReact, useState, useRef } from 'react'; +import { KGToneBuffersPool } from './core/audio-interface/KGToneBuffersPool'; + +function App() { + // Enable global keyboard handler for copy/paste and undo/redo + useGlobalKeyboardHandler(); + + // Use project store instead of local state for project name and tracks + const { + refreshStatus, + loadProject, maxBars, showChatBox, showSettings, setShowSettings, initializeFromConfig, + showInstrumentSelection + } = useProjectStore(); + + // Load project when component mounts + useEffect(() => { + const initializeApp = async () => { + // Load the current project from KGCore + loadProject(null); + + // Initialize store from config after ConfigManager is ready + await initializeFromConfig(); + + // Log maxBars to console + console.log(`Project max bars: ${maxBars}`); + }; + + initializeApp(); + }, [loadProject, maxBars, initializeFromConfig]); + + // Refresh status periodically to ensure UI is in sync with KGCore + useEffect(() => { + // Initial refresh + refreshStatus(); + + // Set up interval to refresh status every second + const intervalId = setInterval(() => { + refreshStatus(); + }, 1000); + + // Clean up interval on unmount + return () => clearInterval(intervalId); + }, [refreshStatus]); + + return ( +
+ {/* Top Toolbar */} + + + {/* Main Display Area containing MainContent, ChatBox, and Settings */} +
+ {showSettings ? ( + setShowSettings(false)} /> + ) : ( + <> + {showInstrumentSelection && } + + {showChatBox && } + + )} +
+ + {/* Track Control */} + + + {/* Status Bar */} + + + {/* Global Loading Overlay for instrument buffer loading */} + +
+ ); +} + +export default App; + +// Local component to subscribe to pool events and manage a counter +const GlobalLoadingOverlayContainer: React.FC = () => { + const [loadingCount, setLoadingCount] = useState(() => KGToneBuffersPool.instance().getActiveLoadCount()); + const [overdue, setOverdue] = useState(false); + const timeoutRef = useRef(null); + + useEffectReact(() => { + const pool = KGToneBuffersPool.instance(); + const listener = (evt: { type: 'start' | 'end'; instrument: string }) => { + setLoadingCount(prev => { + if (evt.type === 'start') return prev + 1; + return Math.max(0, prev - 1); + }); + }; + pool.addLoadingListener(listener); + return () => { + pool.removeLoadingListener(listener); + }; + }, []); + + // Handle long-running overlay: after 10s, hide and alert the user, but allow future loads to show overlay again + useEffectReact(() => { + // When loading starts, start a 30s timer if not already overdue/timed + if (loadingCount > 0 && !overdue && timeoutRef.current === null) { + timeoutRef.current = window.setTimeout(() => { + // Only trigger if still loading + if (loadingCount > 0) { + setOverdue(true); + // Friendly alert to the user + window.alert( + 'Loading resources is taking longer than expected and may have partially failed. If you notice any playback issues, please refresh the page to retry downloading the audio files.' + ); + } + // Clear the timeout handle + timeoutRef.current = null; + }, 10000); + } + + // When loading finishes, clear timer and reset overdue state + if (loadingCount === 0) { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + if (overdue) setOverdue(false); + } + + // Cleanup on unmount: clear any pending timer + return () => { + if (timeoutRef.current !== null) { + clearTimeout(timeoutRef.current); + timeoutRef.current = null; + } + }; + }, [loadingCount, overdue]); + + return ( + 0 && !overdue} + message={loadingCount > 1 ? `Loading ... (${loadingCount})` : 'Loading ...'} + /> + ); +}; diff --git a/src/agent/core/AgentCore.ts b/src/agent/core/AgentCore.ts new file mode 100644 index 0000000..6c4a213 --- /dev/null +++ b/src/agent/core/AgentCore.ts @@ -0,0 +1,176 @@ +import { LLMProvider } from '../llm/LLMProvider'; +import { AgentState } from './AgentState'; +import { SystemPrompts } from './SystemPrompts'; +import type { StreamChunk } from '../llm/StreamingTypes'; + +/** + * Main orchestrator for the AI agent system + */ +export class AgentCore { + private static _instance: AgentCore | null = null; + + private llmProvider: LLMProvider | null = null; + private agentState: AgentState; + private currentUserMessageId: string | null = null; + private currentAssistantMessageId: string | null = null; + + private constructor() { + this.agentState = new AgentState(); + } + + /** + * Get the singleton instance + */ + static instance(): AgentCore { + if (!AgentCore._instance) { + AgentCore._instance = new AgentCore(); + } + return AgentCore._instance; + } + + /** + * Set the LLM provider + */ + setLLMProvider(provider: LLMProvider): void { + this.llmProvider = provider; + } + + /** + * Get the current LLM provider + */ + getLLMProvider(): LLMProvider | null { + return this.llmProvider; + } + + /** + * Get the agent state + */ + getAgentState(): AgentState { + return this.agentState; + } + + /** + * Process user input and generate streaming response + */ + async *processUserInput(userInput: string): AsyncIterableIterator { + if (!this.llmProvider) { + throw new Error('No LLM provider configured'); + } + + // Add user message to state and track its ID + this.currentUserMessageId = this.agentState.addMessage('user', userInput); + + // Get system prompt with current context + const systemPrompt = await SystemPrompts.getSystemPromptWithContext(); + + // Get full conversation history with preserved roles + const conversationHistory = this.agentState.getMessages(); + + // Generate streaming response with full conversation context + let assistantResponse = ''; + + // Pre-add an empty assistant message that we'll update as we stream + this.currentAssistantMessageId = this.agentState.addMessage('assistant', ''); + + try { + for await (const chunk of this.llmProvider.generateStream(conversationHistory, systemPrompt)) { + if (chunk.type === 'text') { + assistantResponse += chunk.content; + // Update the assistant message in real-time + this.agentState.updateMessage(this.currentAssistantMessageId, assistantResponse); + } + yield chunk; + } + + // Final update to ensure the complete response is stored + if (assistantResponse) { + this.agentState.updateMessage(this.currentAssistantMessageId, assistantResponse); + } + } finally { + // Clear the current message IDs when done (successfully or not) + this.currentUserMessageId = null; + this.currentAssistantMessageId = null; + } + } + + /** + * Process user input and get complete response (non-streaming) + */ + async processUserInputComplete(userInput: string): Promise { + if (!this.llmProvider) { + throw new Error('No LLM provider configured'); + } + + // Add user message to state + this.agentState.addMessage('user', userInput); + + // Get system prompt with current context + const systemPrompt = await SystemPrompts.getSystemPromptWithContext(); + + // Get full conversation history with preserved roles + const conversationHistory = this.agentState.getMessages(); + + // Generate complete response with full conversation context + const response = await this.llmProvider.generateCompletion(conversationHistory, systemPrompt); + + // Add assistant response to state + this.agentState.addMessage('assistant', response.content); + + return response.content; + } + + /** + * Abort the current streaming request and clean up messages + * Returns the content of the user message that was aborted (for restoring to input) + */ + abortCurrentRequest(): string | null { + let userMessageContent = null; + + // Remove the current assistant message (the "in progress" one) + if (this.currentAssistantMessageId) { + this.agentState.removeMessage(this.currentAssistantMessageId); + this.currentAssistantMessageId = null; + } + + // Remove the current user message and get its content for restoration + if (this.currentUserMessageId) { + const messages = this.agentState.getMessages(); + const userMessage = messages.find(msg => msg.id === this.currentUserMessageId); + if (userMessage) { + userMessageContent = userMessage.content; + } + this.agentState.removeMessage(this.currentUserMessageId); + this.currentUserMessageId = null; + } + + return userMessageContent; + } + + /** + * Check if there's a current streaming request in progress + */ + isStreamingInProgress(): boolean { + return this.currentUserMessageId !== null && this.currentAssistantMessageId !== null; + } + + /** + * Clear the conversation history + */ + clearConversation(): void { + this.agentState.clearMessages(); + } + + /** + * Get whether the agent is currently working on a task + */ + getIsWorkingOnTask(): boolean { + return this.agentState.getIsWorkingOnTask(); + } + + /** + * Set whether the agent is currently working on a task + */ + setIsWorkingOnTask(isWorking: boolean): void { + this.agentState.setIsWorkingOnTask(isWorking); + } +} \ No newline at end of file diff --git a/src/agent/core/AgentState.ts b/src/agent/core/AgentState.ts new file mode 100644 index 0000000..f7f405e --- /dev/null +++ b/src/agent/core/AgentState.ts @@ -0,0 +1,112 @@ +/** + * Manages the state of an agent conversation + */ + +export interface Message { + id: string; + role: 'user' | 'assistant'; + content: string; + timestamp: number; +} + +export class AgentState { + private messages: Message[] = []; + private conversationId: string; + private isWorkingOnTask: boolean = false; + + constructor(conversationId?: string, isWorkingOnTask: boolean = false) { + this.conversationId = conversationId || this.generateConversationId(); + this.isWorkingOnTask = isWorkingOnTask; + } + + /** + * Add a message to the conversation + */ + addMessage(role: 'user' | 'assistant', content: string): string { + const message: Message = { + id: this.generateMessageId(), + role, + content, + timestamp: Date.now() + }; + + this.messages.push(message); + return message.id; + } + + /** + * Update the content of a message by ID + */ + updateMessage(messageId: string, content: string): boolean { + const messageIndex = this.messages.findIndex(msg => msg.id === messageId); + if (messageIndex !== -1) { + this.messages[messageIndex].content = content; + return true; + } + return false; + } + + /** + * Remove a message by ID + */ + removeMessage(messageId: string): boolean { + const messageIndex = this.messages.findIndex(msg => msg.id === messageId); + if (messageIndex !== -1) { + this.messages.splice(messageIndex, 1); + return true; + } + return false; + } + + /** + * Remove the last N messages + */ + removeLastMessages(count: number): void { + this.messages.splice(-count, count); + } + + /** + * Get all messages in the conversation + */ + getMessages(): Message[] { + return [...this.messages]; + } + + /** + * Get the conversation ID + */ + getConversationId(): string { + return this.conversationId; + } + + /** + * Clear all messages + */ + clearMessages(): void { + this.messages = []; + } + + /** + * Get the last N messages + */ + getRecentMessages(count: number): Message[] { + return this.messages.slice(-count); + } + + private generateConversationId(): string { + return `conv_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; + } + + private generateMessageId(): string { + return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; + } + + // getters and setters + getIsWorkingOnTask(): boolean { + return this.isWorkingOnTask; + } + + setIsWorkingOnTask(isWorkingOnTask: boolean): void { + this.isWorkingOnTask = isWorkingOnTask; + } +} \ No newline at end of file diff --git a/src/agent/core/SystemPrompts.ts b/src/agent/core/SystemPrompts.ts new file mode 100644 index 0000000..c731ea3 --- /dev/null +++ b/src/agent/core/SystemPrompts.ts @@ -0,0 +1,266 @@ +import { KGCore } from '../../core/KGCore'; +import { KGRegion } from '../../core/region/KGRegion'; +import { KGMidiTrack } from '../../core/track/KGMidiTrack'; +import { KGTrack } from '../../core/track/KGTrack'; +import { useProjectStore } from '../../stores/projectStore'; +import { ConfigManager } from '../../core/config/ConfigManager'; +import { FLUIDR3_INSTRUMENT_MAP } from '../../constants/generalMidiConstants'; + +/** + * Context data structure for system prompt template replacement + */ +interface SystemPromptContext { + bpm: number; + time_signature: string; + key_signature: string; + track_instrument: string; + current_region_start: number; + current_region_end: number; +} + +/** + * System prompts for the AI agent with dynamic context loading + */ +export class SystemPrompts { + private static cachedTemplate: string | null = null; + private static readonly FALLBACK_PROMPT = `You are K.G.Studio Musician Assistant Agent, a highly skilled music musician with extensive knowledge in music theory, composition, and production.`; + + /** + * Load the system prompt template from the public folder + */ + private static async loadTemplate(): Promise { + if (this.cachedTemplate) { + return this.cachedTemplate; + } + + try { + const response = await fetch('/prompts/system.md'); + if (!response.ok) { + throw new Error(`Failed to load system prompt: ${response.status}`); + } + + this.cachedTemplate = await response.text(); + return this.cachedTemplate; + } catch (error) { + console.error('Failed to load system prompt template:', error); + return this.FALLBACK_PROMPT; + } + } + + /** + * Find a region by ID across all tracks + */ + private static findRegionById(regionId: string): KGRegion | null { + const core = KGCore.instance(); + const project = core.getCurrentProject(); + const tracks = project.getTracks(); + + for (const track of tracks) { + const regions = track.getRegions(); + const region = regions.find(r => r.getId() === regionId); + if (region) { + return region; + } + } + + return null; + } + + /** + * Find track that contains the given region + */ + private static findTrackByRegion(region: KGRegion): KGTrack | null { + const core = KGCore.instance(); + const project = core.getCurrentProject(); + const tracks = project.getTracks(); + + return tracks.find(track => track.getRegions().includes(region)) || null; + } + + /** + * Extract current project context from KGCore + */ + private static extractProjectContext(): Partial { + const core = KGCore.instance(); + const project = core.getCurrentProject(); + + // Get basic project info + const context: Partial = { + bpm: project.getBpm(), + time_signature: `${project.getTimeSignature().numerator}/${project.getTimeSignature().denominator}`, + key_signature: project.getKeySignature(), + }; + + // Get current track instrument using the corrected logic + let trackInstrument = 'Piano'; // Default + + // Step 1: Check if there's an active piano roll region + const activeRegionId = this.getActiveRegionId(); + if (activeRegionId) { + const activeRegion = this.findRegionById(activeRegionId); + if (activeRegion) { + const track = this.findTrackByRegion(activeRegion); + if (track && track instanceof KGMidiTrack) { + trackInstrument = FLUIDR3_INSTRUMENT_MAP[track.getInstrument()].displayName; + } + } + } else { + // Step 2: Check if user has selected region(s) + const selectedItems = core.getSelectedItems(); + const selectedRegion = selectedItems.find(item => item instanceof KGRegion) as KGRegion; + + if (selectedRegion) { + const track = this.findTrackByRegion(selectedRegion); + if (track && track instanceof KGMidiTrack) { + trackInstrument = FLUIDR3_INSTRUMENT_MAP[track.getInstrument()].displayName; + } + } else { + // Step 3: Find the first track + const tracks = project.getTracks(); + const firstMidiTrack = tracks.find(track => track instanceof KGMidiTrack) as KGMidiTrack; + if (firstMidiTrack) { + trackInstrument = FLUIDR3_INSTRUMENT_MAP[firstMidiTrack.getInstrument()].displayName; + } + } + } + + context.track_instrument = trackInstrument; + return context; + } + + /** + * Get active region ID from project store + */ + private static getActiveRegionId(): string | null { + try { + const store = useProjectStore.getState(); + return store.activeRegionId; + } catch { + return null; + } + } + + /** + * Extract current region context with fallback logic + */ + private static extractRegionContext(): Partial { + const core = KGCore.instance(); + const project = core.getCurrentProject(); + + // Step 1: Try active piano roll region + const activeRegionId = this.getActiveRegionId(); + if (activeRegionId) { + const activeRegion = this.findRegionById(activeRegionId); + if (activeRegion) { + return { + current_region_start: activeRegion.getStartFromBeat(), + current_region_end: activeRegion.getStartFromBeat() + activeRegion.getLength(), + }; + } + } + + // Step 2: Try selected region + const selectedItems = core.getSelectedItems(); + const selectedRegion = selectedItems.find(item => item instanceof KGRegion) as KGRegion; + + if (selectedRegion) { + return { + current_region_start: selectedRegion.getStartFromBeat(), + current_region_end: selectedRegion.getStartFromBeat() + selectedRegion.getLength(), + }; + } + + // Step 3: Fallback to project bounds + const timeSignature = project.getTimeSignature(); + const beatsPerBar = timeSignature.numerator; + const maxBars = project.getMaxBars(); + + return { + current_region_start: 0, + current_region_end: maxBars * beatsPerBar, + }; + } + + /** + * Get full context by combining project and region data + */ + private static getFullContext(): SystemPromptContext { + const projectContext = this.extractProjectContext(); + const regionContext = this.extractRegionContext(); + + return { + bpm: projectContext.bpm || 120, + time_signature: projectContext.time_signature || '4/4', + key_signature: projectContext.key_signature || 'C major', + track_instrument: projectContext.track_instrument || 'Piano', + current_region_start: regionContext.current_region_start || 0, + current_region_end: regionContext.current_region_end || 32, + }; + } + + /** + * Replace template variables with actual context values + */ + static replaceTemplateVariables(template: string, context: SystemPromptContext): string { + let result = template; + + // Replace all context variables + result = result.replace(/{bpm}/g, context.bpm.toString()); + result = result.replace(/{time_signature}/g, context.time_signature); + result = result.replace(/{key_signature}/g, context.key_signature); + result = result.replace(/{track_instrument}/g, context.track_instrument); + result = result.replace(/{current_region_start}/g, context.current_region_start.toString()); + result = result.replace(/{current_region_end}/g, context.current_region_end.toString()); + + return result; + } + + /** + * Apply context to an arbitrary prompt string + */ + static async getPromptWithContext(prompt: string): Promise { + try { + const context = this.getFullContext(); + return this.replaceTemplateVariables(prompt, context); + } catch (error) { + console.error('Error generating prompt with context:', error); + return prompt; + } + } + + /** + * Get the system prompt with current context applied (backward compatible) + */ + static async getSystemPromptWithContext(): Promise { + try { + const template = await this.loadTemplate(); + let promptWithContext = await this.getPromptWithContext(template); + + // Append custom instructions from config if provided + try { + const configManager = ConfigManager.instance(); + if (!configManager.getIsInitialized()) { + await configManager.initialize(); + } + const customInstructions = ((configManager.get('templates.custom_instructions') as string) || '').trim(); + if (customInstructions.length > 0) { + promptWithContext += `\n\n====\n\nADDITIONAL INSTRUCTIONS\n\n${customInstructions}`; + } + } catch (e) { + console.warn('Failed to load custom instructions from config:', e); + } + + return promptWithContext; + } catch (error) { + console.error('Error generating system prompt with context:', error); + return this.FALLBACK_PROMPT; + } + } + + /** + * Clear the cached template (useful for development/testing) + */ + static clearCache(): void { + this.cachedTemplate = null; + } +} \ No newline at end of file diff --git a/src/agent/core/XMLToolExecutor.ts b/src/agent/core/XMLToolExecutor.ts new file mode 100644 index 0000000..6694ccf --- /dev/null +++ b/src/agent/core/XMLToolExecutor.ts @@ -0,0 +1,389 @@ +/** + * XMLToolExecutor - Bridge between XML tool invocations and the existing tool system + * Parses XML blocks from LLM responses and executes corresponding tools + */ + +import { extractXMLFromString } from '../../util/xmlUtil'; +import { AVAILABLE_TOOLS, type ToolName } from '../tools'; +import type { BaseTool, ToolResult } from '../tools/BaseTool'; +import { useProjectStore } from '../../stores/projectStore'; + +/** + * Main executor class for XML-based tool invocations + * Integrates with existing tool architecture and streaming types + */ +export class XMLToolExecutor { + // Private static instance for singleton pattern + private static _instance: XMLToolExecutor | null = null; + + // Private constructor to prevent direct instantiation + private constructor() {} + + /** + * Get the singleton instance of XMLToolExecutor + */ + public static instance(): XMLToolExecutor { + if (!XMLToolExecutor._instance) { + XMLToolExecutor._instance = new XMLToolExecutor(); + } + return XMLToolExecutor._instance; + } + + /** + * Execute all XML tool invocations found in the given input string + * @param input - String containing XML tool invocations (typically LLM response) + * @returns Promise resolving to array of tool results in order of appearance + */ + public async executeXMLTools(input: string): Promise { + try { + // Extract all XML blocks from the input + const xmlBlocks = extractXMLFromString(input); + + if (xmlBlocks.length === 0) { + return []; + } + + // Process each XML block and collect results + const results: ToolResult[] = []; + + for (const xmlBlock of xmlBlocks) { + try { + const result = await this.executeXMLBlock(xmlBlock); + results.push(result); + } catch (error) { + // Create failed result + results.push({ + success: false, + result: `Failed to process XML block: ${error}` + }); + } + } + + return results; + + } catch (error) { + return [{ + success: false, + result: `Failed to execute XML tools: ${error}` + }]; + } + } + + /** + * Execute a single XML block as a tool invocation + * @param xmlBlock - XML string representing a tool invocation + * @returns Promise resolving to tool execution result + */ + private async executeXMLBlock(xmlBlock: string): Promise { + // Parse XML to extract tool information + const parseResult = this.parseXMLBlock(xmlBlock); + + if (!parseResult.success) { + return { + success: false, + result: parseResult.error || 'Failed to parse XML block' + }; + } + + // Check if tool exists in registry + if (!(parseResult.toolName in AVAILABLE_TOOLS)) { + return { + success: false, + result: `Unknown tool: ${parseResult.toolName}` + }; + } + + try { + // Create tool instance + const ToolClass = AVAILABLE_TOOLS[parseResult.toolName as ToolName]; + const toolInstance: BaseTool = new ToolClass(); + + // Execute the tool + const toolResult = await toolInstance.execute(parseResult.parameters); + + // Sync UI state if the tool execution was successful + if (toolResult.success) { + this.syncUIState(); + } + + return toolResult; + + } catch (error) { + return { + success: false, + result: `Tool execution failed: ${error}` + }; + } + } + + /** + * Parse XML block to extract tool name and parameters + * @param xmlBlock - XML string to parse + * @returns Parse result with tool information or error + */ + private parseXMLBlock(xmlBlock: string): { success: boolean; toolName: string; parameters: Record; error?: string } { + try { + // Special pre-processing for attempt_completion: ensure is wrapped in CDATA + const preparedXml = this.preprocessAttemptCompletionXML(xmlBlock); + + // Parse XML using native DOMParser + const parser = new DOMParser(); + const doc = parser.parseFromString(preparedXml, 'text/xml'); + + // Check for parsing errors + const parserError = doc.querySelector('parsererror'); + if (parserError) { + return { + success: false, + toolName: '', + parameters: {}, + error: `XML parsing error: ${parserError.textContent}` + }; + } + + // Get the root element (tool name) + const rootElement = doc.documentElement; + const toolName = rootElement.tagName; + + // Parse XML parameters + const parameters = this.parseXMLParameters(rootElement); + + return { + success: true, + toolName, + parameters + }; + + } catch (error) { + return { + success: false, + toolName: '', + parameters: {}, + error: `Failed to parse XML: ${error}` + }; + } + } + + /** + * Ensure special tools have CDATA-wrapped content where appropriate. + * - attempt_completion: wrap inner text with CDATA (if not already) + * - think / thinking: wrap root inner text with CDATA (if not already) + * Decode basic XML entities before wrapping so CDATA contains human-readable text. + */ + private preprocessAttemptCompletionXML(xml: string): string { + try { + const leadingWhitespaceMatch = xml.match(/^\s*/); + const prefix = leadingWhitespaceMatch ? leadingWhitespaceMatch[0] : ''; + const withoutLeading = xml.slice(prefix.length); + const rootMatch = withoutLeading.match(/^<([A-Za-z_][\w-]*)\b/); + const root = rootMatch?.[1] || ''; + if (root !== 'attempt_completion' && root !== 'think' && root !== 'thinking') return xml; + + // Helper to decode entities + const decodeEntities = (text: string): string => + text + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'"); + + if (root === 'attempt_completion') { + // Find first ... + const commentRegex = /([\s\S]*?)<\/comment>/i; + const match = xml.match(commentRegex); + if (!match) return xml; + + const inner = match[1]; + if (/`; + return xml.replace(commentRegex, replacement); + } + + // Handle ... or ... + const rootRegex = new RegExp(`<${root}>([\\s\\S]*?)`, 'i'); + const rootMatchContent = xml.match(rootRegex); + if (!rootMatchContent) return xml; + const innerRoot = rootMatchContent[1]; + if (/`; + return xml.replace(rootRegex, replacementRoot); + } catch { + // On any error, return original XML to avoid breaking flow + return xml; + } + } + + /** + * Parse XML element into tool parameters object + * Converts XML structure to JavaScript object that matches tool parameter schema + * @param element - Root XML element containing tool parameters + * @returns Parameters object for tool execution + */ + private parseXMLParameters(element: Element): Record { + const parameters: Record = {}; + + // Special handling for thinking tool: if no child elements, use text content directly + if (element.tagName === 'thinking' && element.children.length === 0) { + const textContent = element.textContent?.trim() || ''; + parameters.content = textContent; + return parameters; + } + + // Process all child elements + for (const child of element.children) { + const paramName = child.tagName; + const paramValue = this.parseXMLValue(child); + + // Handle arrays (multiple elements with same tag name) + if (parameters[paramName] !== undefined) { + // Convert to array if not already + if (!Array.isArray(parameters[paramName])) { + parameters[paramName] = [parameters[paramName]]; + } + (parameters[paramName] as unknown[]).push(paramValue); + } else { + parameters[paramName] = paramValue; + } + } + + // Apply array wrapper flattening + return this.flattenArrayWrappers(parameters); + } + + /** + * Flatten array wrapper patterns in parsed parameters + * Converts structures like {notes: {note: [...]}} to {notes: [...]} + * @param parameters - Parsed parameters object + * @returns Parameters with flattened array wrappers + */ + private flattenArrayWrappers(parameters: Record): Record { + const flattened: Record = {}; + + for (const [key, value] of Object.entries(parameters)) { + if (this.isArrayWrapperCandidate(key, value)) { + // This is an array wrapper - flatten it + const wrapperObj = value as Record; + const innerKeys = Object.keys(wrapperObj); + + if (innerKeys.length === 1) { + const innerKey = innerKeys[0]; + const innerValue = wrapperObj[innerKey]; + + // Check if inner key is singular form of outer key + if (this.isSingularOf(innerKey, key)) { + // Flatten: {notes: {note: [...]}} → {notes: [...]} + flattened[key] = innerValue; + continue; + } + } + } + + // No flattening needed, keep as is + flattened[key] = value; + } + + return flattened; + } + + /** + * Check if a value is a candidate for array wrapper flattening + * @param key - Parameter key (e.g., "notes") + * @param value - Parameter value to check + * @returns True if this looks like an array wrapper pattern + */ + private isArrayWrapperCandidate(key: string, value: unknown): boolean { + // Must be an object (not array, not primitive) + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + return false; + } + + const obj = value as Record; + const innerKeys = Object.keys(obj); + + // Must have exactly one property + if (innerKeys.length !== 1) { + return false; + } + + const innerKey = innerKeys[0]; + const innerValue = obj[innerKey]; + + // Inner value should be an array or could become an array + // (single items are often converted to arrays by the parser) + return this.isSingularOf(innerKey, key) && + (Array.isArray(innerValue) || typeof innerValue === 'object'); + } + + /** + * Check if one word is the singular form of another (simple heuristic) + * @param singular - Potential singular form (e.g., "note") + * @param plural - Potential plural form (e.g., "notes") + * @returns True if singular appears to be singular form of plural + */ + private isSingularOf(singular: string, plural: string): boolean { + // Simple heuristics for common English pluralization + if (plural === singular + 's') return true; // note → notes + if (plural === singular + 'es') return true; // box → boxes + if (plural.endsWith('ies') && singular.endsWith('y')) { // entry → entries + return plural === singular.slice(0, -1) + 'ies'; + } + + // Add more rules as needed for your specific use cases + return false; + } + + /** + * Parse a single XML element value, handling different data types and structures + * @param element - XML element to parse + * @returns Parsed value (string, number, boolean, object, or array) + */ + private parseXMLValue(element: Element): unknown { + // If element has children, parse as object + if (element.children.length > 0) { + return this.parseXMLParameters(element); + } + + // Get text content + const textContent = element.textContent?.trim() || ''; + + // Try to parse as number + if (/^-?\d+(\.\d+)?$/.test(textContent)) { + return parseFloat(textContent); + } + + // Try to parse as boolean + if (textContent === 'true') return true; + if (textContent === 'false') return false; + + // Return as string + return textContent; + } + + /** + * Synchronize UI state after successful tool execution + * Uses the centralized refresh method from the project store + */ + private syncUIState(): void { + try { + // Use the centralized refresh method from the store + const storeActions = useProjectStore.getState(); + if (storeActions.refreshProjectState) { + storeActions.refreshProjectState(); + } + + } catch (error) { + console.warn('Failed to sync UI state after XML tool execution:', error); + // Don't throw - UI sync failure shouldn't break tool execution + } + } + +} \ No newline at end of file diff --git a/src/agent/llm/ClaudeProvider.ts b/src/agent/llm/ClaudeProvider.ts new file mode 100644 index 0000000..9b5faa6 --- /dev/null +++ b/src/agent/llm/ClaudeProvider.ts @@ -0,0 +1,229 @@ +import { LLMProvider } from './LLMProvider'; +import type { StreamChunk, LLMResponse } from './StreamingTypes'; +import type { Message } from '../core/AgentState'; +import { ConfigManager } from '../../core/config/ConfigManager'; + +/** + * Anthropic Claude API provider implementation + */ +export class ClaudeProvider extends LLMProvider { + readonly name = 'Claude'; + + private apiKey: string; + private model: string; + private baseURL: string = 'https://api.anthropic.com'; + private apiEndpoint: string; + + constructor() { + super(); + + const configManager = ConfigManager.instance(); + this.apiKey = configManager.get('general.claude.api_key') as string; + this.model = configManager.get('general.claude.model') as string; + this.apiEndpoint = `${this.baseURL}/v1/messages`; + } + + /** + * Convert internal messages to Claude's format + */ + private convertMessages(messages: Message[], systemPrompt?: string): { + system?: string; + messages: Array<{ role: 'user' | 'assistant'; content: string }>; + } { + const claudeMessages: Array<{ role: 'user' | 'assistant'; content: string }> = []; + + claudeMessages.push(...messages.map(msg => ({ role: msg.role, content: msg.content }))); + + return { + system: systemPrompt, + messages: claudeMessages + }; + } + + /** + * Parse Claude's streaming response chunks + */ + private parseClaudeStreamChunk(line: string): { content?: string; isDone?: boolean } { + if (!line.startsWith('data: ')) { + return {}; + } + + const data = line.slice(6); + if (data === '[DONE]') { + return { isDone: true }; + } + + try { + const json = JSON.parse(data); + + // Handle different Claude streaming event types + switch (json.type) { + case 'content_block_delta': + return { + content: json.delta?.text, + isDone: false + }; + case 'message_stop': + return { isDone: true }; + default: + return {}; + } + } catch { + return {}; // Skip invalid JSON lines + } + } + + async *generateStream( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): AsyncIterableIterator { + const { system, messages: claudeMessages } = this.convertMessages(messages, systemPrompt); + + const requestBody: { + model: string; + max_tokens: number; + messages: Array<{ role: 'user' | 'assistant'; content: string }>; + stream: boolean; + system?: string; + tools?: Record[]; + } = { + model: this.model, + max_tokens: 8192, + messages: claudeMessages, + stream: true + }; + + if (system) { + requestBody.system = system; + } + + if (tools && tools.length > 0) { + requestBody.tools = tools; + } + + const response = await fetch(this.apiEndpoint, { + method: 'POST', + headers: { + 'x-api-key': this.apiKey, + 'Content-Type': 'application/json', + 'anthropic-version': '2023-06-01' + }, + body: JSON.stringify(requestBody), + mode: 'cors' + }); + + if (!response.ok) { + throw new Error(`Claude API error: ${response.status} ${response.statusText}`); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('Failed to get response reader'); + } + + const decoder = new TextDecoder(); + let buffer = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + const trimmedLine = line.trim(); + if (!trimmedLine) continue; + + const parseResult = this.parseClaudeStreamChunk(trimmedLine); + + if (parseResult.isDone) { + yield { type: 'done', content: '' }; + return; + } + + if (parseResult.content) { + yield { + type: 'text', + content: parseResult.content + }; + } + } + } + } finally { + reader.releaseLock(); + } + } + + async generateCompletion( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): Promise { + const { system, messages: claudeMessages } = this.convertMessages(messages, systemPrompt); + + const requestBody: { + model: string; + max_tokens: number; + messages: Array<{ role: 'user' | 'assistant'; content: string }>; + stream: boolean; + system?: string; + tools?: Record[]; + } = { + model: this.model, + max_tokens: 8192, + messages: claudeMessages, + stream: false + }; + + if (system) { + requestBody.system = system; + } + + if (tools && tools.length > 0) { + requestBody.tools = tools; + } + + const response = await fetch(this.apiEndpoint, { + method: 'POST', + headers: { + 'x-api-key': this.apiKey, + 'Content-Type': 'application/json', + 'anthropic-version': '2023-06-01' + }, + body: JSON.stringify(requestBody), + mode: 'cors' + }); + + if (!response.ok) { + throw new Error(`Claude API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + + // Extract content from Claude's response format + const content = data.content + ?.filter((block: { type: string }) => block.type === 'text') + ?.map((block: { text: string }) => block.text) + ?.join('') || ''; + + // Extract tool calls if present + const toolCalls = data.content + ?.filter((block: { type: string }) => block.type === 'tool_use') + ?.map((block: { id: string; name: string; input: Record }) => ({ + id: block.id, + name: block.name, + parameters: block.input + })) || []; + + return { + content, + toolCalls: toolCalls.length > 0 ? toolCalls : undefined, + finished: data.stop_reason === 'end_turn' || data.stop_reason === 'tool_use' + }; + } +} \ No newline at end of file diff --git a/src/agent/llm/GeminiProvider.ts b/src/agent/llm/GeminiProvider.ts new file mode 100644 index 0000000..7a770b1 --- /dev/null +++ b/src/agent/llm/GeminiProvider.ts @@ -0,0 +1,245 @@ +import { LLMProvider } from './LLMProvider'; +import type { StreamChunk, LLMResponse } from './StreamingTypes'; +import type { Message } from '../core/AgentState'; +import { ConfigManager } from '../../core/config/ConfigManager'; + +/** + * Google Gemini API provider implementation + */ +export class GeminiProvider extends LLMProvider { + readonly name = 'Gemini'; + + private apiKey: string; + private model: string; + private baseURL: string = 'https://generativelanguage.googleapis.com'; + private apiEndpoint: string; + + constructor() { + super(); + + const configManager = ConfigManager.instance(); + this.apiKey = configManager.get('general.gemini.api_key') as string; + this.model = configManager.get('general.gemini.model') as string; + this.apiEndpoint = `${this.baseURL}/v1beta/models/${this.model}`; + } + + /** + * Convert internal messages to Gemini's format + */ + private convertMessages(messages: Message[], systemPrompt?: string): { + systemInstruction?: { parts: Array<{ text: string }> }; + contents: Array<{ role: 'user' | 'model'; parts: Array<{ text: string }> }>; + } { + const geminiMessages: Array<{ role: 'user' | 'model'; parts: Array<{ text: string }> }> = []; + + for (const msg of messages) { + let role: 'user' | 'model'; + + if (msg.role === 'assistant') { + role = 'model'; + } else { + // Treat system and user messages as 'user' role + role = 'user'; + } + + geminiMessages.push({ + role, + parts: [{ text: msg.content }] + }); + } + + const result: { + systemInstruction?: { parts: Array<{ text: string }> }; + contents: Array<{ role: 'user' | 'model'; parts: Array<{ text: string }> }>; + } = { + contents: geminiMessages + }; + + if (systemPrompt) { + result.systemInstruction = { + parts: [{ text: systemPrompt }] + }; + } + + return result; + } + + /** + * Parse Gemini's streaming response chunks + */ + private parseGeminiStreamChunk(chunk: string): { content?: string; isDone?: boolean } { + try { + const json = JSON.parse(chunk.trim()); + + // Gemini streaming format + if (json.candidates && json.candidates.length > 0) { + const candidate = json.candidates[0]; + + // Check if generation is finished + if (candidate.finishReason && candidate.finishReason !== 'STOP') { + return { isDone: true }; + } + + // Extract text content + const content = candidate.content?.parts?.[0]?.text; + if (content) { + return { content, isDone: false }; + } + } + + // Check for explicit done signal + if (json.done === true) { + return { isDone: true }; + } + + return {}; + } catch { + return {}; // Skip invalid JSON + } + } + + async *generateStream( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): AsyncIterableIterator { + const { systemInstruction, contents } = this.convertMessages(messages, systemPrompt); + + const requestBody: { + contents: Array<{ role: 'user' | 'model'; parts: Array<{ text: string }> }>; + generationConfig: { temperature: number; maxOutputTokens: number }; + systemInstruction?: { parts: Array<{ text: string }> }; + tools?: Record[]; + } = { + contents, + generationConfig: { + temperature: 0.7, + maxOutputTokens: 8192, + } + }; + + if (systemInstruction) { + requestBody.systemInstruction = systemInstruction; + } + + if (tools && tools.length > 0) { + requestBody.tools = tools; + } + + const response = await fetch(`${this.apiEndpoint}:streamGenerateContent?key=${this.apiKey}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + throw new Error(`Gemini API error: ${response.status} ${response.statusText}`); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('Failed to get response reader'); + } + + const decoder = new TextDecoder(); + let buffer = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + // Gemini sends JSON objects separated by newlines + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + const trimmedLine = line.trim(); + if (!trimmedLine) continue; + + const parseResult = this.parseGeminiStreamChunk(trimmedLine); + + if (parseResult.isDone) { + yield { type: 'done', content: '' }; + return; + } + + if (parseResult.content) { + yield { + type: 'text', + content: parseResult.content + }; + } + } + } + } finally { + reader.releaseLock(); + } + } + + async generateCompletion( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): Promise { + const { systemInstruction, contents } = this.convertMessages(messages, systemPrompt); + + const requestBody: { + contents: Array<{ role: 'user' | 'model'; parts: Array<{ text: string }> }>; + generationConfig: { temperature: number; maxOutputTokens: number }; + systemInstruction?: { parts: Array<{ text: string }> }; + tools?: Record[]; + } = { + contents, + generationConfig: { + temperature: 0.7, + maxOutputTokens: 8192, + } + }; + + if (systemInstruction) { + requestBody.systemInstruction = systemInstruction; + } + + if (tools && tools.length > 0) { + requestBody.tools = tools; + } + + const response = await fetch(`${this.apiEndpoint}:generateContent?key=${this.apiKey}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + throw new Error(`Gemini API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + + // Extract content from Gemini's response format + const candidate = data.candidates?.[0]; + const content = candidate?.content?.parts?.[0]?.text || ''; + + // Extract tool calls if present (Gemini format) + const toolCalls = candidate?.content?.parts + ?.filter((part: { functionCall?: unknown }) => part.functionCall) + ?.map((part: { functionCall: { name: string; args: Record } }) => ({ + id: `tool_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`, // Generate ID + name: part.functionCall.name, + parameters: part.functionCall.args + })) || []; + + return { + content, + toolCalls: toolCalls.length > 0 ? toolCalls : undefined, + finished: candidate?.finishReason === 'STOP' || candidate?.finishReason === 'MAX_TOKENS' + }; + } +} \ No newline at end of file diff --git a/src/agent/llm/LLMProvider.ts b/src/agent/llm/LLMProvider.ts new file mode 100644 index 0000000..67d6ee0 --- /dev/null +++ b/src/agent/llm/LLMProvider.ts @@ -0,0 +1,33 @@ +import type { StreamChunk, LLMResponse } from './StreamingTypes'; +import type { Message } from '../core/AgentState'; + +/** + * Abstract interface for LLM providers + */ +export abstract class LLMProvider { + abstract name: string; + + /** + * Generate a streaming response from the LLM + * @param messages The full conversation history with preserved roles + * @param systemPrompt The system prompt (optional, can be included in messages) + * @param tools Available tools (optional for now) + */ + abstract generateStream( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): AsyncIterableIterator; + + /** + * Generate a complete response from the LLM (non-streaming) + * @param messages The full conversation history with preserved roles + * @param systemPrompt The system prompt (optional, can be included in messages) + * @param tools Available tools (optional for now) + */ + abstract generateCompletion( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): Promise; +} \ No newline at end of file diff --git a/src/agent/llm/OpenAIProvider.ts b/src/agent/llm/OpenAIProvider.ts new file mode 100644 index 0000000..8365c96 --- /dev/null +++ b/src/agent/llm/OpenAIProvider.ts @@ -0,0 +1,454 @@ +import { LLMProvider } from './LLMProvider'; +import type { StreamChunk, LLMResponse } from './StreamingTypes'; +import type { Message } from '../core/AgentState'; +import { ConfigManager } from '../../core/config/ConfigManager'; +import { URL_CONSTANTS } from '../../constants/coreConstants'; + +/** + * OpenAI API provider implementation + */ +export class OpenAIProvider extends LLMProvider { + readonly name = 'OpenAI'; + + private apiKey: string; + private model: string; + private flexMode: boolean = false; + private baseURL: string; + private isCompatibleProvider: boolean; + private apiEndpoint: string; + private isOllamaFormat: boolean | null = null; // Detected at runtime + + constructor() { + super(); + + const configManager = ConfigManager.instance(); + const llmProvider = configManager.get('general.llm_provider') as string; + this.isCompatibleProvider = llmProvider === 'openai_compatible'; + + // Set API key, model, base URL, and endpoint based on provider type + if (this.isCompatibleProvider) { + this.apiKey = configManager.get('general.openai_compatible.api_key') as string; + this.model = configManager.get('general.openai_compatible.model') as string; + this.baseURL = configManager.get('general.openai_compatible.base_url') as string; + // For compatible providers, use the full URL as provided (assume it includes the endpoint) + // Common patterns: http://localhost:11434/api/chat (Ollama), https://api.openrouter.ai/v1 (OpenRouter) + this.apiEndpoint = this.baseURL; + this.flexMode = false; // Not applicable to compatible providers + } else { + this.apiKey = configManager.get('general.openai.api_key') as string; + this.model = configManager.get('general.openai.model') as string; + this.flexMode = (configManager.get('general.openai.flex') as boolean) === true; + this.baseURL = URL_CONSTANTS.DEFAULT_OPENAI_BASE_URL; + this.apiEndpoint = `${this.baseURL}/chat/completions`; + } + } + + /** + * Detect if the response uses Ollama's raw JSON format or OpenAI's SSE format + */ + private detectStreamFormat(firstChunk: string): boolean { + // If it starts with "data: ", it's OpenAI SSE format + if (firstChunk.trim().startsWith('data: ')) { + return false; // Not Ollama format + } + + // Try to parse as JSON - if successful and has 'done' field, it's Ollama format + try { + const json = JSON.parse(firstChunk.trim()); + return typeof json.done === 'boolean'; + } catch { + return false; // Not valid JSON, assume OpenAI format + } + } + + /** + * Parse Ollama's raw JSON chunk format + */ + private parseOllamaChunk(chunk: string): { thinking?: string; content?: string; isDone?: boolean; toolCalls?: Array<{ name: string; arguments: Record }> } { + try { + const json = JSON.parse(chunk.trim()); + const thinking: string | undefined = json.message?.thinking; + const content: string | undefined = json.message?.content || json.response; // Handle both chat and completion formats + type WireToolCall = { function?: { name?: unknown; arguments?: unknown } }; + const toolCallsRaw: unknown[] | undefined = json.message?.tool_calls as unknown[] | undefined; + const toolCalls = Array.isArray(toolCallsRaw) + ? (toolCallsRaw + .map((tc: unknown) => { + const wire = tc as WireToolCall; + const fn = wire?.function; + if (!fn || typeof fn.name !== 'string') return null; + const args = fn.arguments; + if (args === null || typeof args !== 'object' || Array.isArray(args)) return null; + return { name: fn.name, arguments: args as Record }; + }) + .filter((v): v is { name: string; arguments: Record } => v !== null)) + : undefined; + return { + thinking, + content, + isDone: json.done === true, + toolCalls + }; + } catch { + return {}; // Invalid JSON, return empty object + } + } + + /** + * Parse OpenAI's SSE format chunk + */ + private parseOpenAIChunk(line: string): { thinking?: string; content?: string; isDone?: boolean; toolCallDelta?: Array<{ index?: number; function?: { name?: string; arguments?: string } }> } { + if (!line.startsWith('data: ')) { + return {}; + } + + const data = line.slice(6); + if (data === '[DONE]') { + return { isDone: true }; + } + + try { + const json = JSON.parse(data); + const delta = json.choices?.[0]?.delta; + const thinking: string | undefined = delta?.thinking; // Some providers may stream "thinking" + const content: string | undefined = delta?.content; + const tcd = delta?.tool_calls; + const toolCallDelta: Array<{ index?: number; function?: { name?: string; arguments?: string } }> | undefined = Array.isArray(tcd) + ? (tcd as Array<{ index?: number; function?: { name?: string; arguments?: string } }>) + : undefined; + return { thinking, content, isDone: false, toolCallDelta }; + } catch { + return {}; // Skip invalid JSON lines + } + } + + async *generateStream( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): AsyncIterableIterator { + // Build OpenAI messages array with role preservation + const openAIMessages: Array<{ role: string; content: string }> = []; + + // Add system prompt if provided + if (systemPrompt) { + openAIMessages.push({ role: 'system', content: systemPrompt }); + } + + // Add conversation history with preserved roles + openAIMessages.push(...messages.map(msg => ({ + role: msg.role, + content: msg.content + }))); + + const response = await fetch(this.apiEndpoint, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: this.model, + ...(this.flexMode && !this.isCompatibleProvider ? { service_tier: 'flex' } : {}), + messages: openAIMessages, + stream: true, + tools: tools || undefined + }), + }); + + if (!response.ok) { + throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`); + } + + const reader = response.body?.getReader(); + if (!reader) { + throw new Error('Failed to get response reader'); + } + + const decoder = new TextDecoder(); + let buffer = ''; + let firstChunkProcessed = false; + let lastSegmentType: 'thinking' | 'content' | null = null; + const pendingFunctionCalls: Array<{ name: string; arguments: Record }> = []; + const openAIToolCallBuilders: Record = {}; + + const escapeXml = (text: string): string => + String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + + const functionCallToXml = (name: string, args: Record): string => { + const keys = Object.keys(args); + const inner = keys + .map((k) => { + const value = (args as Record)[k]; + const text = typeof value === 'string' ? value : JSON.stringify(value); + return `<${k}>${escapeXml(text)}`; + }) + .join('\n'); + return `<${name}>\n${inner}\n`; + }; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + + // For Ollama format, we need to split by newlines for JSON objects + // For OpenAI format, we also split by newlines for SSE + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + const trimmedLine = line.trim(); + if (!trimmedLine) continue; + + // Detect format on first non-empty chunk + if (!firstChunkProcessed) { + this.isOllamaFormat = this.detectStreamFormat(trimmedLine); + firstChunkProcessed = true; + } + + if (this.isOllamaFormat) { + const { thinking, content, isDone, toolCalls } = this.parseOllamaChunk(trimmedLine); + if (isDone) { + // Append any pending or current tool calls as XML before finishing + const allToolCalls = [ + ...pendingFunctionCalls, + ...(toolCalls || []) + ]; + if (allToolCalls.length > 0) { + const xmlBlocks = allToolCalls + .map((tc) => `\n${functionCallToXml(tc.name, tc.arguments)}\n`) + .join(''); + yield { type: 'text', content: xmlBlocks }; + } + yield { type: 'done', content: '' }; + return; + } + + if (typeof thinking === 'string' && thinking.length > 0) { + if (lastSegmentType && lastSegmentType !== 'thinking') { + yield { type: 'text', content: '\n\n' }; + } + yield { type: 'text', content: thinking }; + lastSegmentType = 'thinking'; + } + + if (typeof content === 'string' && content.length > 0) { + if (lastSegmentType && lastSegmentType !== 'content') { + yield { type: 'text', content: '\n\n' }; + } + yield { type: 'text', content: content }; + lastSegmentType = 'content'; + } + + if (Array.isArray(toolCalls) && toolCalls.length > 0) { + // Accumulate and append at the end of stream + pendingFunctionCalls.push(...toolCalls); + } + continue; + } + + const { thinking, content, isDone, toolCallDelta } = this.parseOpenAIChunk(trimmedLine); + if (isDone) { + // Finalize any accumulated OpenAI tool calls and emit XML + const finalizedToolCalls: Array<{ name: string; arguments: Record }> = []; + for (const indexStr of Object.keys(openAIToolCallBuilders)) { + const idx = Number(indexStr); + const builder = openAIToolCallBuilders[idx]; + if (!builder || !builder.name) continue; + let argsObj: Record | null = null; + if (builder.argumentsText) { + try { + argsObj = JSON.parse(builder.argumentsText); + } catch { + argsObj = null; + } + } + if (argsObj) { + finalizedToolCalls.push({ name: builder.name, arguments: argsObj }); + } + } + + const allToolCalls = [...pendingFunctionCalls, ...finalizedToolCalls]; + if (allToolCalls.length > 0) { + const xmlBlocks = allToolCalls + .map((tc) => `\n${functionCallToXml(tc.name, tc.arguments)}\n`) + .join(''); + yield { type: 'text', content: xmlBlocks }; + } + yield { type: 'done', content: '' }; + return; + } + + if (typeof thinking === 'string' && thinking.length > 0) { + if (lastSegmentType && lastSegmentType !== 'thinking') { + yield { type: 'text', content: '\n\n' }; + } + yield { type: 'text', content: thinking }; + lastSegmentType = 'thinking'; + } + + if (typeof content === 'string' && content.length > 0) { + if (lastSegmentType && lastSegmentType !== 'content') { + yield { type: 'text', content: '\n\n' }; + } + yield { type: 'text', content }; + lastSegmentType = 'content'; + } + + if (Array.isArray(toolCallDelta) && toolCallDelta.length > 0) { + for (const tc of toolCallDelta) { + const index: number = typeof tc.index === 'number' ? tc.index : 0; + if (!openAIToolCallBuilders[index]) { + openAIToolCallBuilders[index] = { argumentsText: '' }; + } + const fn = tc.function; + if (fn) { + if (typeof fn.name === 'string') { + openAIToolCallBuilders[index].name = fn.name; + } + if (typeof fn.arguments === 'string') { + openAIToolCallBuilders[index].argumentsText += fn.arguments; + } + } + } + } + } + } + } finally { + reader.releaseLock(); + } + } + + async generateCompletion( + messages: Message[], + systemPrompt?: string, + tools?: Record[] + ): Promise { + // Build OpenAI messages array with role preservation + const openAIMessages: Array<{ role: string; content: string }> = []; + + // Add system prompt if provided + if (systemPrompt) { + openAIMessages.push({ role: 'system', content: systemPrompt }); + } + + // Add conversation history with preserved roles + openAIMessages.push(...messages.map(msg => ({ + role: msg.role, + content: msg.content + }))); + + const response = await fetch(this.apiEndpoint, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${this.apiKey}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + model: this.model, + ...(this.flexMode && !this.isCompatibleProvider ? { service_tier: 'flex' } : {}), + messages: openAIMessages, + stream: false, + tools: tools || undefined + }), + }); + + if (!response.ok) { + throw new Error(`OpenAI API error: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + + // Handle different response formats + const escapeXml = (text: string): string => + String(text) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + + const functionCallToXml = (name: string, args: Record): string => { + const keys = Object.keys(args); + const inner = keys + .map((k) => { + const value = (args as Record)[k]; + const text = typeof value === 'string' ? value : JSON.stringify(value); + return `<${k}>${escapeXml(text)}`; + }) + .join('\n'); + return `<${name}>\n${inner}\n`; + }; + + if (data.message) { + // Ollama/compatible format + const thinking: string = data.message.thinking || ''; + const contentText: string = data.message.content || data.response || ''; + const textCombined = thinking && contentText ? `${thinking}\n\n${contentText}` : (thinking || contentText); + + type WireToolCall = { function?: { name?: unknown; arguments?: unknown } }; + const toolCallsRaw: unknown[] | undefined = data.message.tool_calls as unknown[] | undefined; + const toolCalls: Array<{ name: string; arguments: Record }> = Array.isArray(toolCallsRaw) + ? (toolCallsRaw + .map((tc: unknown) => { + const wire = tc as WireToolCall; + const fn = wire?.function; + if (!fn || typeof fn.name !== 'string') return null; + const args = fn.arguments; + if (args === null || typeof args !== 'object' || Array.isArray(args)) return null; + return { name: fn.name, arguments: args as Record }; + }) + .filter((v): v is { name: string; arguments: Record } => v !== null)) + : []; + + const xmlBlocks = toolCalls.length > 0 + ? toolCalls.map((tc) => `\n${functionCallToXml(tc.name, tc.arguments)}\n`).join('') + : ''; + + return { + content: `${textCombined}${xmlBlocks}`, + toolCalls: data.message.tool_calls || undefined, + finished: data.done === true || data.done_reason === 'stop' + }; + } else { + // OpenAI format + const choice = data.choices?.[0]; + const thinking: string = choice?.message?.thinking || ''; + const contentText: string = choice?.message?.content || ''; + const textCombined = thinking && contentText ? `${thinking}\n\n${contentText}` : (thinking || contentText); + + type WireToolCall2 = { function?: { name?: unknown; arguments?: unknown } }; + const toolCallsRaw: unknown[] | undefined = choice?.message?.tool_calls as unknown[] | undefined; + const toolCalls: Array<{ name: string; arguments: Record }> = Array.isArray(toolCallsRaw) + ? (toolCallsRaw + .map((tc: unknown) => { + const wire = tc as WireToolCall2; + const fn = wire?.function; + if (!fn || typeof fn.name !== 'string') return null; + const args = fn.arguments; + if (args === null || typeof args !== 'object' || Array.isArray(args)) return null; + return { name: fn.name, arguments: args as Record }; + }) + .filter((v): v is { name: string; arguments: Record } => v !== null)) + : []; + + const xmlBlocks = toolCalls.length > 0 + ? toolCalls.map((tc) => `\n${functionCallToXml(tc.name, tc.arguments)}\n`).join('') + : ''; + + return { + content: `${textCombined}${xmlBlocks}`, + toolCalls: choice?.message?.tool_calls || undefined, + finished: choice?.finish_reason === 'stop' || choice?.finish_reason === 'tool_calls' + }; + } + } +} \ No newline at end of file diff --git a/src/agent/llm/StreamingTypes.ts b/src/agent/llm/StreamingTypes.ts new file mode 100644 index 0000000..d53c4d1 --- /dev/null +++ b/src/agent/llm/StreamingTypes.ts @@ -0,0 +1,27 @@ +/** + * Types for streaming LLM responses and tool execution + */ + +import type { ToolResult } from '../tools/BaseTool'; + +// Re-export for convenience +export type { ToolResult }; + +export interface ToolInvocation { + id: string; + name: string; + parameters: Record; +} + +export interface StreamChunk { + type: 'text' | 'tool_call' | 'tool_result' | 'done'; + content: string; + toolCall?: ToolInvocation; + toolResult?: ToolResult; +} + +export interface LLMResponse { + content: string; + toolCalls?: ToolInvocation[]; + finished: boolean; +} \ No newline at end of file diff --git a/src/agent/tools/AddNotesTool.ts b/src/agent/tools/AddNotesTool.ts new file mode 100644 index 0000000..f5de003 --- /dev/null +++ b/src/agent/tools/AddNotesTool.ts @@ -0,0 +1,235 @@ +import { BaseTool } from './BaseTool'; +import type { ToolResult, ToolParameter } from './BaseTool'; +import { CreateNotesCommand } from '../../core/commands/note/CreateNotesCommand'; +import type { NoteCreationData } from '../../core/commands/note/CreateNotesCommand'; +import { KGMidiRegion } from '../../core/region/KGMidiRegion'; +import { useProjectStore } from '../../stores/projectStore'; +import { KGCore } from '../../core/KGCore'; + +/** + * Tool for adding notes to MIDI regions + * Integrates with the existing command system for undo/redo support + */ +export class AddNotesTool extends BaseTool { + readonly name = 'add_notes'; + readonly description = 'Create one or more MIDI notes in the current region. Each note requires pitch (e.g., "C4", "F#3"), start_beat (beat position), and length (duration in beats).'; + + readonly parameters: Record = { + notes: { + type: 'array', + description: 'Array of notes to create', + required: true, + items: { + type: 'object', + description: 'A MIDI note definition', + properties: { + pitch: { + type: 'string', + description: 'Note pitch in scientific notation (e.g., "C4", "F#3", "Bb2")', + required: true + }, + start_beat: { + type: 'number', + description: 'Start position in beats (e.g., 0, 1.5, 2)', + required: true + }, + length: { + type: 'number', + description: 'Note duration in beats (e.g., 1, 0.5, 4)', + required: true + }, + velocity: { + type: 'number', + description: 'Note velocity (1-127, default: 127)', + required: false + } + } + } + }, + region_id: { + type: 'string', + description: 'ID of the region to add notes to. If not provided, uses the currently selected region.', + required: false + } + }; + + async execute(params: Record): Promise { + try { + // Validate parameters + this.validateParameters(params); + + const notes = params.notes as Array<{ + pitch: string; + start_beat: number; + length: number; + velocity?: number; + }>; + + const regionId = params.region_id as string | undefined; + + // Find the target region + const targetRegion = this.findTargetRegion(regionId); + if (!targetRegion) { + return this.createErrorResult( + regionId + ? `Region with ID "${regionId}" not found or is not a MIDI region` + : 'No active or selected MIDI region found. Please open the piano roll with a region or select a MIDI region first.' + ); + } + + // Validate and convert notes to creation data + const noteCreationData: NoteCreationData[] = []; + const createdNotes: Array<{ pitch: string; start_beat: number; length: number }> = []; + + for (const note of notes) { + try { + const midiPitch = this.convertPitchToMidi(note.pitch); + const velocity = note.velocity ?? 127; + + // Validate velocity range + if (velocity < 1 || velocity > 127) { + return this.createErrorResult(`Invalid velocity ${velocity}. Must be between 1 and 127.`); + } + + // Validate beat positions + if (note.start_beat < 0) { + return this.createErrorResult(`Invalid start_beat ${note.start_beat}. Must be >= 0.`); + } + + if (note.length <= 0) { + return this.createErrorResult(`Invalid length ${note.length}. Must be > 0.`); + } + + // Adjust note position relative to region's start beat + const regionStartBeat = targetRegion.getStartFromBeat(); + const adjustedStartBeat = note.start_beat - regionStartBeat; + const adjustedEndBeat = adjustedStartBeat + note.length; + + // Create note creation data + noteCreationData.push({ + regionId: targetRegion.getId(), + startBeat: adjustedStartBeat, + endBeat: adjustedEndBeat, + pitch: midiPitch, + velocity + }); + + createdNotes.push({ + pitch: note.pitch, + start_beat: note.start_beat, + length: note.length + }); + + } catch (error) { + return this.createErrorResult(`Invalid note pitch "${note.pitch}": ${error}`); + } + } + + // Execute the bulk note creation command + const command = new CreateNotesCommand(noteCreationData); + await this.executeCommand(command); + + // Create success message + const noteCount = createdNotes.length; + const noteList = createdNotes + .map(note => `${note.pitch} (beat ${note.start_beat}, length ${note.length})`) + .join(', '); + + return this.createSuccessResult( + `Successfully created ${noteCount} note${noteCount > 1 ? 's' : ''}: ${noteList}` + ); + + } catch (error) { + return this.createErrorResult(`Failed to create notes: ${error}`); + } + } + + /** + * Find the target region for note creation + * Priority: 1) Specified regionId, 2) Active piano roll region, 3) Selected regions, 4) Error if none found + */ + private findTargetRegion(regionId?: string): KGMidiRegion | null { + const project = this.getCurrentProject(); + const tracks = project.getTracks(); + + if (regionId) { + // Find specific region by ID + for (const track of tracks) { + const regions = track.getRegions(); + const region = regions.find(r => r.getId() === regionId); + if (region && region instanceof KGMidiRegion) { + return region; + } + } + return null; + } else { + // Smart region finding: try different sources in priority order + + // 1. Try active piano roll region + const storeState = useProjectStore.getState(); + if (storeState.activeRegionId) { + for (const track of tracks) { + const regions = track.getRegions(); + const region = regions.find(r => r.getId() === storeState.activeRegionId); + if (region && region instanceof KGMidiRegion) { + return region; + } + } + } + + // 2. Try selected regions + const core = this.getKGCore(); + const selectedItems = core.getSelectedItems(); + for (const item of selectedItems) { + if (item instanceof KGMidiRegion) { + return item; + } + } + + // 3. No fallback - return null to trigger error + return null; + } + } + + /** + * Get KGCore instance for selection access + */ + private getKGCore() { + return KGCore.instance(); + } + + /** + * Convert pitch string to MIDI note number + * Supports formats like: C4, F#3, Bb2, C#5 + */ + private convertPitchToMidi(pitch: string): number { + const match = pitch.match(/^([A-G])([#b]?)(\d+)$/); + if (!match) { + throw new Error(`Invalid pitch format "${pitch}". Use format like "C4", "F#3", "Bb2"`); + } + + const [, noteName, accidental, octaveStr] = match; + const octave = parseInt(octaveStr); + + // Base MIDI notes for C octave (C4 = 60) + const noteOffsets: Record = { + 'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11 + }; + + let midiNote = (octave + 1) * 12 + noteOffsets[noteName]; + + // Apply accidentals + if (accidental === '#') { + midiNote += 1; + } else if (accidental === 'b') { + midiNote -= 1; + } + + // Validate MIDI range + if (midiNote < 0 || midiNote > 127) { + throw new Error(`Note "${pitch}" is out of MIDI range (0-127)`); + } + + return midiNote; + } +} \ No newline at end of file diff --git a/src/agent/tools/AttemptCompletionTool.ts b/src/agent/tools/AttemptCompletionTool.ts new file mode 100644 index 0000000..be8da7d --- /dev/null +++ b/src/agent/tools/AttemptCompletionTool.ts @@ -0,0 +1,49 @@ +import { BaseTool } from './BaseTool'; +import type { ToolResult, ToolParameter } from './BaseTool'; +import { AgentCore } from '../core/AgentCore'; + +/** + * Tool for signaling task completion + * This is a pure agent state tool that doesn't modify the DAW but signals + * to the agent system that the user's requested task has been completed + */ +export class AttemptCompletionTool extends BaseTool { + readonly name = 'attempt_completion'; + readonly description = 'Signal that the current user task is fully complete. Only use this when you have successfully fulfilled all aspects of the user\'s request.'; + + readonly parameters: Record = { + comment: { + type: 'string', + description: 'A brief comment describing what was completed and any relevant details about the task fulfillment.', + required: true + } + }; + + async execute(params: Record): Promise { + try { + // Validate parameters + this.validateParameters(params); + + const comment = params.comment as string; + + // Validate comment is not empty + if (!comment.trim()) { + return this.createErrorResult('Comment cannot be empty. Please provide a meaningful completion summary.'); + } + + // Get current agent state and update task completion status + const agentCore = AgentCore.instance(); + const agentState = agentCore.getAgentState(); + + // Mark that we're no longer working on a task + agentState.setIsWorkingOnTask(false); + + return this.createSuccessResult( + `Task completed: ${comment}. Agent task status updated to not working.` + ); + + } catch (error) { + return this.createErrorResult(`Failed to mark task as complete: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/agent/tools/BaseTool.ts b/src/agent/tools/BaseTool.ts new file mode 100644 index 0000000..4f3215f --- /dev/null +++ b/src/agent/tools/BaseTool.ts @@ -0,0 +1,162 @@ +import { KGCommand } from '../../core/commands/KGCommand'; +import { KGCore } from '../../core/KGCore'; + +/** + * Result of tool execution + */ +export interface ToolResult { + success: boolean; + result: string; +} + +/** + * Parameter definition for tool parameters + */ +export interface ToolParameter { + type: 'string' | 'number' | 'boolean' | 'array' | 'object'; + description: string; + required?: boolean; + items?: ToolParameter; // For array types + properties?: Record; // For object types +} + +/** + * Tool definition schema + */ +export interface ToolDefinition { + name: string; + description: string; + parameters: Record; +} + +/** + * Abstract base class for all agent tools + * Provides integration with the existing command system and core architecture + */ +export abstract class BaseTool { + abstract readonly name: string; + abstract readonly description: string; + abstract readonly parameters: Record; + + /** + * Execute the tool with given parameters + * @param params Tool parameters + * @returns Promise resolving to tool execution result + */ + abstract execute(params: Record): Promise; + + /** + * Get the tool definition in OpenAI function calling format + */ + getDefinition(): ToolDefinition { + return { + name: this.name, + description: this.description, + parameters: this.parameters + }; + } + + /** + * Validate parameters against the tool's parameter schema + * @param params Parameters to validate + * @returns True if valid, throws error if invalid + */ + protected validateParameters(params: Record): boolean { + for (const [paramName, paramDef] of Object.entries(this.parameters)) { + const value = params[paramName]; + + // Check required parameters + if (paramDef.required && (value === undefined || value === null)) { + throw new Error(`Required parameter '${paramName}' is missing`); + } + + // Skip type checking for undefined optional parameters + if (value === undefined || value === null) { + continue; + } + + // Type validation + if (!this.validateParameterType(value, paramDef)) { + throw new Error(`Parameter '${paramName}' has invalid type. Expected: ${paramDef.type}`); + } + } + + return true; + } + + /** + * Validate a single parameter value against its type definition + */ + private validateParameterType(value: unknown, paramDef: ToolParameter): boolean { + switch (paramDef.type) { + case 'string': + return typeof value === 'string'; + case 'number': + return typeof value === 'number'; + case 'boolean': + return typeof value === 'boolean'; + case 'array': + if (!Array.isArray(value)) return false; + if (paramDef.items) { + return value.every(item => this.validateParameterType(item, paramDef.items!)); + } + return true; + case 'object': + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + return false; + } + if (paramDef.properties) { + const obj = value as Record; + for (const [propName, propDef] of Object.entries(paramDef.properties)) { + if (propDef.required && !(propName in obj)) { + return false; + } + if (propName in obj && !this.validateParameterType(obj[propName], propDef)) { + return false; + } + } + } + return true; + default: + return false; + } + } + + /** + * Execute a command through the existing command system + * This provides undo/redo functionality and proper state management + * @param command Command to execute + */ + protected async executeCommand(command: KGCommand): Promise { + const core = KGCore.instance(); + return core.executeCommand(command); + } + + /** + * Get the current project from KGCore + */ + protected getCurrentProject() { + const core = KGCore.instance(); + return core.getCurrentProject(); + } + + /** + * Create a successful tool result + */ + protected createSuccessResult(result: string): ToolResult { + return { + success: true, + result + }; + } + + /** + * Create a failed tool result + */ + protected createErrorResult(result: string): ToolResult { + return { + success: false, + result + }; + } +} \ No newline at end of file diff --git a/src/agent/tools/ReadMusicTool.ts b/src/agent/tools/ReadMusicTool.ts new file mode 100644 index 0000000..3f27738 --- /dev/null +++ b/src/agent/tools/ReadMusicTool.ts @@ -0,0 +1,299 @@ +import { BaseTool } from './BaseTool'; +import type { ToolResult, ToolParameter } from './BaseTool'; +import { KGMidiTrack } from '../../core/track/KGMidiTrack'; +import { KGMidiRegion } from '../../core/region/KGMidiRegion'; +import { convertRegionToABCNotation } from '../../util/abcNotationUtil'; +import { KEY_SIGNATURE_MAP } from '../../constants/coreConstants'; +import { useProjectStore } from '../../stores/projectStore'; +import { KGRegion } from '../../core/region/KGRegion'; +import { KGCore } from '../../core/KGCore'; + +/** + * Tool for reading music content from the project + * Provides read-only access to project data and converts to ABC notation + */ +export class ReadMusicTool extends BaseTool { + readonly name = 'read_music'; + readonly description = 'Read the music content from a specific track or all tracks, returning the content in ABC notation format.'; + + readonly parameters: Record = { + track_id: { + type: 'string', + description: 'The track ID to read, or "all" to read all tracks. If not provided, reads the first available track.', + required: false + }, + start_beat: { + type: 'number', + description: 'Start beat position to read from (default: 0)', + required: false + }, + length: { + type: 'number', + description: 'Length in beats to read (default: entire track/project)', + required: false + } + }; + + async execute(params: Record): Promise { + try { + // Validate parameters + this.validateParameters(params); + + const trackId = params.track_id as string | undefined; + const startBeat = (params.start_beat as number) || 0; + const length = params.length as number | undefined; + + const project = this.getCurrentProject(); + const tracks = project.getTracks(); + + if (tracks.length === 0) { + return this.createErrorResult('No tracks found in the project'); + } + + // Validate start_beat + if (startBeat < 0) { + return this.createErrorResult(`Invalid start_beat ${startBeat}. Must be >= 0.`); + } + + // Validate length + if (length !== undefined && length <= 0) { + return this.createErrorResult(`Invalid length ${length}. Must be > 0.`); + } + + // Get project settings for bar rounding + const timeSignature = project.getTimeSignature(); + const beatsPerBar = timeSignature.numerator; + + // Round startBeat to floor bar beats and calculate endBeat + const roundedStartBeat = Math.floor(startBeat / beatsPerBar) * beatsPerBar; + const rawEndBeat = length !== undefined ? startBeat + length : undefined; + const roundedEndBeat = rawEndBeat !== undefined ? Math.ceil(rawEndBeat / beatsPerBar) * beatsPerBar : undefined; + + let abcOutput = ''; + + if (!trackId || trackId === '' || trackId === 'all') { + // Read all tracks + const midiTracks = tracks.filter(track => track instanceof KGMidiTrack) as KGMidiTrack[]; + abcOutput = this.generateAllTracksABC(midiTracks, roundedStartBeat, roundedEndBeat); + } else { + // Read specific track or first available track + const targetTrack = trackId + ? tracks.find(t => t.getId().toString() === trackId) + : tracks[0]; + + if (!targetTrack) { + return this.createErrorResult( + trackId + ? `Track with ID "${trackId}" not found` + : 'No tracks available' + ); + } + + if (!(targetTrack instanceof KGMidiTrack)) { + return this.createErrorResult(`Track "${targetTrack.getName()}" is not a MIDI track`); + } + + abcOutput = this.generateSingleTrackABC(targetTrack, roundedStartBeat, roundedEndBeat); + } + + return this.createSuccessResult(abcOutput); + + } catch (error) { + return this.createErrorResult(`Failed to read music: ${error}`); + } + } + + /** + * Get KGCore instance + */ + private getKGCore(): KGCore { + return KGCore.instance(); + } + + /** + * Find the track that contains the active piano roll region or first selected region + */ + private findTrackToSkip(tracks: KGMidiTrack[]): KGMidiTrack | null { + try { + const store = useProjectStore.getState(); + const core = this.getKGCore(); + + // First check for active piano roll region + if (store.activeRegionId) { + const activeRegion = this.findRegionById(store.activeRegionId, tracks); + if (activeRegion) { + const track = this.findTrackByRegion(activeRegion, tracks); + return track; + } + } + + // Then check for selected regions + const selectedItems = core.getSelectedItems(); + const selectedRegion = selectedItems.find((item: unknown) => item instanceof KGRegion) as KGRegion; + + if (selectedRegion) { + const track = this.findTrackByRegion(selectedRegion, tracks); + return track; + } + + return null; + } catch (error) { + console.error('Error finding track to skip:', error); + return null; + } + } + + /** + * Find a region by ID across all tracks + */ + private findRegionById(regionId: string, tracks: KGMidiTrack[]): KGRegion | null { + for (const track of tracks) { + const regions = track.getRegions(); + const region = regions.find(r => r.getId() === regionId); + if (region) { + return region; + } + } + return null; + } + + /** + * Find track that contains the given region + */ + private findTrackByRegion(region: KGRegion, tracks: KGMidiTrack[]): KGMidiTrack | null { + return tracks.find(track => track.getRegions().includes(region)) || null; + } + + /** + * Generate ABC notation for all tracks + */ + private generateAllTracksABC(tracks: KGMidiTrack[], startBeat: number, endBeat?: number): string { + const midiTracks = tracks.filter(track => track instanceof KGMidiTrack); + + if (midiTracks.length === 0) { + return 'No MIDI tracks found in the project.'; + } + + // Find the track to skip (unless it's the first track) + const trackToSkip = this.findTrackToSkip(midiTracks); + const firstTrack = midiTracks[0]; // The melody track + + // Get project settings for proper notation + const project = this.getCurrentProject(); + const timeSignature = project.getTimeSignature(); + const keySignature = project.getKeySignature(); + const abcKeySignature = KEY_SIGNATURE_MAP[keySignature]?.abcNotationKeySignature || 'C'; + + let output = `All Tracks (beats ${startBeat}-${endBeat || 'end'}):\n\n`; + + midiTracks.forEach((track, index) => { + // Skip this track if it's the track to skip AND it's not the first track (melody) + if (trackToSkip && track === trackToSkip && track !== firstTrack) { + return; // Skip this track + } + const trackNumber = index + 1; + const trackName = track.getName() || `Track ${trackNumber}`; + + // hardcode the 1st track to be the melody, other track names are the same as the original track names + output += `Track ${trackNumber} - ${trackNumber === 1 ? 'Melody' : trackName}:\n`; + + // Get all regions from the track and convert each one + const regions = track.getRegions().filter(region => region instanceof KGMidiRegion) as KGMidiRegion[]; + + if (regions.length === 0) { + output += 'X:' + trackNumber + '\n'; + output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`; + output += `K:${abcKeySignature}\n`; + output += 'z4 | // No regions found\n\n'; + } else { + // Convert each region that overlaps with the requested range + let hasContent = false; + regions.forEach((region) => { + const regionStart = region.getStartFromBeat(); + const regionEnd = regionStart + region.getLength(); + + // Check if region overlaps with requested range + if (regionStart < (endBeat || Infinity) && regionEnd > startBeat) { + const abcNotation = convertRegionToABCNotation(region, startBeat, endBeat); + + // Update the X: line to include track number + const lines = abcNotation.split('\n'); + lines[0] = `X:${trackNumber}`; + output += lines.join('\n') + '\n\n'; + hasContent = true; + } + }); + + if (!hasContent) { + output += 'X:' + trackNumber + '\n'; + output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`; + output += `K:${abcKeySignature}\n`; + output += 'z4 | // No content in specified range\n\n'; + } + } + }); + + return output.trim(); + } + + /** + * Generate ABC notation for a single track + */ + private generateSingleTrackABC(track: KGMidiTrack, startBeat: number, endBeat?: number): string { + if (!(track instanceof KGMidiTrack)) { + return `Track is not a MIDI track.`; + } + + // Get project settings for proper notation + const project = this.getCurrentProject(); + const timeSignature = project.getTimeSignature(); + const keySignature = project.getKeySignature(); + const abcKeySignature = KEY_SIGNATURE_MAP[keySignature]?.abcNotationKeySignature || 'C'; + + const trackName = track.getName() || 'Unnamed Track'; + + let output = `Track "${trackName}" (beats ${startBeat}-${endBeat || 'end'}):\n`; + + // Get all regions from the track and convert each one + const regions = track.getRegions().filter(region => region instanceof KGMidiRegion) as KGMidiRegion[]; + + if (regions.length === 0) { + output += 'X:1\n'; + output += `T:${trackName}\n`; + output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`; + output += `K:${abcKeySignature}\n`; + output += `L:1/${timeSignature.denominator}\n`; + output += 'z4 | // No regions found'; + } else { + // Convert each region that overlaps with the requested range + let hasContent = false; + regions.forEach((region) => { + const regionStart = region.getStartFromBeat(); + const regionEnd = regionStart + region.getLength(); + + // Check if region overlaps with requested range + if (regionStart < (endBeat || Infinity) && regionEnd > startBeat) { + const abcNotation = convertRegionToABCNotation(region, startBeat, endBeat); + + // Update the title to include track name + const lines = abcNotation.split('\n'); + lines[1] = `T:${trackName}`; + output += lines.join('\n'); + hasContent = true; + } + }); + + if (!hasContent) { + output += 'X:1\n'; + output += `T:${trackName}\n`; + output += `M:${timeSignature.numerator}/${timeSignature.denominator}\n`; + output += `K:${abcKeySignature}\n`; + output += `L:1/${timeSignature.denominator}\n`; + output += 'z4 | // No content in specified range'; + } + } + + return output; + } + +} \ No newline at end of file diff --git a/src/agent/tools/RemoveNotesTool.ts b/src/agent/tools/RemoveNotesTool.ts new file mode 100644 index 0000000..ce2c774 --- /dev/null +++ b/src/agent/tools/RemoveNotesTool.ts @@ -0,0 +1,168 @@ +import { BaseTool } from './BaseTool'; +import type { ToolResult, ToolParameter } from './BaseTool'; +import { DeleteNotesCommand } from '../../core/commands/note/DeleteNotesCommand'; +import { KGMidiRegion } from '../../core/region/KGMidiRegion'; +import { useProjectStore } from '../../stores/projectStore'; +import { KGCore } from '../../core/KGCore'; + +/** + * Tool for removing notes from MIDI regions within a specified beat range + * Integrates with the existing command system for undo/redo support + */ +export class RemoveNotesTool extends BaseTool { + readonly name = 'remove_notes'; + readonly description = 'Remove MIDI notes from the current region within a specified beat range. All notes that start within the range will be deleted.'; + + readonly parameters: Record = { + start_beat: { + type: 'number', + description: 'Start of the beat range to remove notes from (inclusive)', + required: true + }, + end_beat: { + type: 'number', + description: 'End of the beat range to remove notes from (exclusive)', + required: true + }, + region_id: { + type: 'string', + description: 'ID of the region to remove notes from. If not provided, uses the currently selected region.', + required: false + } + }; + + async execute(params: Record): Promise { + try { + // Validate parameters + this.validateParameters(params); + + const startBeat = params.start_beat as number; + const endBeat = params.end_beat as number; + const regionId = params.region_id as string | undefined; + + // Validate beat range + if (startBeat < 0) { + return this.createErrorResult(`Invalid start_beat ${startBeat}. Must be >= 0.`); + } + + if (endBeat <= startBeat) { + return this.createErrorResult(`Invalid beat range: end_beat (${endBeat}) must be greater than start_beat (${startBeat}).`); + } + + // Find the target region + const targetRegion = this.findTargetRegion(regionId); + if (!targetRegion) { + return this.createErrorResult( + regionId + ? `Region with ID "${regionId}" not found or is not a MIDI region` + : 'No active or selected MIDI region found. Please open the piano roll with a region or select a MIDI region first.' + ); + } + + // Adjust beat range relative to region's start beat + const regionStartBeat = targetRegion.getStartFromBeat(); + const adjustedStartBeat = startBeat - regionStartBeat; + const adjustedEndBeat = endBeat - regionStartBeat; + + // Find all notes within the specified beat range + const notesToRemove = this.findNotesInRange(targetRegion, adjustedStartBeat, adjustedEndBeat); + + if (notesToRemove.length === 0) { + return this.createSuccessResult( + `No notes found in the range from beat ${startBeat} to ${endBeat}.` + ); + } + + // Extract note IDs for deletion + const noteIds = notesToRemove.map(note => note.getId()); + + // Execute the deletion command + const command = new DeleteNotesCommand(noteIds); + await this.executeCommand(command); + + // Create success message + const noteCount = notesToRemove.length; + const noteList = notesToRemove + .map(note => { + const noteNames = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; + const octave = Math.floor(note.getPitch() / 12) - 1; + const noteName = noteNames[note.getPitch() % 12]; + return `${noteName}${octave}`; + }) + .join(', '); + + return this.createSuccessResult( + `Successfully removed ${noteCount} note${noteCount > 1 ? 's' : ''} from beats ${startBeat}-${endBeat}: ${noteList}` + ); + + } catch (error) { + return this.createErrorResult(`Failed to remove notes: ${error}`); + } + } + + /** + * Find the target region for note removal + * Priority: 1) Specified regionId, 2) Active piano roll region, 3) Selected regions, 4) Error if none found + */ + private findTargetRegion(regionId?: string): KGMidiRegion | null { + const project = this.getCurrentProject(); + const tracks = project.getTracks(); + + if (regionId) { + // Find specific region by ID + for (const track of tracks) { + const regions = track.getRegions(); + const region = regions.find(r => r.getId() === regionId); + if (region && region instanceof KGMidiRegion) { + return region; + } + } + return null; + } else { + // Smart region finding: try different sources in priority order + + // 1. Try active piano roll region + const storeState = useProjectStore.getState(); + if (storeState.activeRegionId) { + for (const track of tracks) { + const regions = track.getRegions(); + const region = regions.find(r => r.getId() === storeState.activeRegionId); + if (region && region instanceof KGMidiRegion) { + return region; + } + } + } + + // 2. Try selected regions + const core = this.getKGCore(); + const selectedItems = core.getSelectedItems(); + for (const item of selectedItems) { + if (item instanceof KGMidiRegion) { + return item; + } + } + + // 3. No fallback - return null to trigger error + return null; + } + } + + /** + * Get KGCore instance for selection access + */ + private getKGCore() { + return KGCore.instance(); + } + + /** + * Find all notes within the specified beat range + * Notes are included if their start beat is within [startBeat, endBeat) + */ + private findNotesInRange(region: KGMidiRegion, startBeat: number, endBeat: number) { + const notes = region.getNotes(); + return notes.filter(note => { + const noteStartBeat = note.getStartBeat(); + return noteStartBeat >= startBeat && noteStartBeat < endBeat; + }); + } +} \ No newline at end of file diff --git a/src/agent/tools/ThinkTool.ts b/src/agent/tools/ThinkTool.ts new file mode 100644 index 0000000..139e3c4 --- /dev/null +++ b/src/agent/tools/ThinkTool.ts @@ -0,0 +1,35 @@ +import { BaseTool } from './BaseTool'; +import type { ToolResult, ToolParameter } from './BaseTool'; + +/** + * Pseudo tool for handling tags in LLM responses + * This tool displays the thinking content in the UI but doesn't send results back to the LLM + * Handles XML format: any content here + * This is functionally identical to ThinkingTool but handles the shorter tag name + */ +export class ThinkTool extends BaseTool { + readonly name = 'think'; + readonly description = 'Pseudo tool for handling LLM thinking content from tags. Shows content in UI but does not send results back to LLM.'; + + readonly parameters: Record = { + content: { + type: 'string', + description: 'The thinking content from the XML tag', + required: false + } + }; + + async execute(params: Record): Promise { + try { + // Extract the thinking content from the parameters + // const content = params.content as string || ''; + + // Return the thinking content as a successful result + // This will be displayed in the UI but not sent back to the LLM + return this.createSuccessResult("Thinking completed."); + + } catch (error) { + return this.createErrorResult(`Failed to process thinking content: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/agent/tools/ThinkingTool.ts b/src/agent/tools/ThinkingTool.ts new file mode 100644 index 0000000..a710b6c --- /dev/null +++ b/src/agent/tools/ThinkingTool.ts @@ -0,0 +1,34 @@ +import { BaseTool } from './BaseTool'; +import type { ToolResult, ToolParameter } from './BaseTool'; + +/** + * Pseudo tool for handling tags in LLM responses + * This tool displays the thinking content in the UI but doesn't send results back to the LLM + * Handles XML format: any content here + */ +export class ThinkingTool extends BaseTool { + readonly name = 'thinking'; + readonly description = 'Pseudo tool for handling LLM thinking content. Shows content in UI but does not send results back to LLM.'; + + readonly parameters: Record = { + content: { + type: 'string', + description: 'The thinking content from the XML tag', + required: false + } + }; + + async execute(params: Record): Promise { + try { + // Extract the thinking content from the parameters + // const content = params.content as string || ''; + + // Return the thinking content as a successful result + // This will be displayed in the UI but not sent back to the LLM + return this.createSuccessResult("Thinking completed."); + + } catch (error) { + return this.createErrorResult(`Failed to process thinking content: ${error}`); + } + } +} \ No newline at end of file diff --git a/src/agent/tools/index.ts b/src/agent/tools/index.ts new file mode 100644 index 0000000..dd75d43 --- /dev/null +++ b/src/agent/tools/index.ts @@ -0,0 +1,25 @@ +// Base tool system +export { BaseTool } from './BaseTool'; +export type { ToolResult, ToolParameter, ToolDefinition } from './BaseTool'; + +// Specific tools +import { AddNotesTool } from './AddNotesTool'; +import { RemoveNotesTool } from './RemoveNotesTool'; +import { ReadMusicTool } from './ReadMusicTool'; +import { AttemptCompletionTool } from './AttemptCompletionTool'; +import { ThinkingTool } from './ThinkingTool'; +import { ThinkTool } from './ThinkTool'; + +export { AddNotesTool, RemoveNotesTool, ReadMusicTool, AttemptCompletionTool, ThinkingTool, ThinkTool }; + +// Tool registry for easy access +export const AVAILABLE_TOOLS = { + add_notes: AddNotesTool, + remove_notes: RemoveNotesTool, + read_music: ReadMusicTool, + attempt_completion: AttemptCompletionTool, + thinking: ThinkingTool, + think: ThinkTool +} as const; + +export type ToolName = keyof typeof AVAILABLE_TOOLS; \ No newline at end of file diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/ChatBox.tsx b/src/components/ChatBox.tsx new file mode 100644 index 0000000..fd389a9 --- /dev/null +++ b/src/components/ChatBox.tsx @@ -0,0 +1,582 @@ +import React, { useState, useRef, useEffect, memo } from 'react'; +import { FaPlus, FaBan } from 'react-icons/fa'; +import { UserMessage, AssistantMessage } from './chat'; +import { AgentCore } from '../agent/core/AgentCore'; +import { OpenAIProvider } from '../agent/llm/OpenAIProvider'; +import { ClaudeProvider } from '../agent/llm/ClaudeProvider'; +import { GeminiProvider } from '../agent/llm/GeminiProvider'; +import { LLMProvider } from '../agent/llm/LLMProvider'; +import { ConfigManager } from '../core/config/ConfigManager'; +import { useProjectStore } from '../stores/projectStore'; +import { XMLToolExecutor } from '../agent/core/XMLToolExecutor'; +import { extractXMLFromString } from '../util/xmlUtil'; +import { SystemPrompts } from '../agent/core/SystemPrompts'; +import { clearChatHistoryAndUI, registerClearChatUICallback } from '../util/chatUtil'; +import { processUserMessage } from '../util/messageFilter/UserMessageFilter'; + +// Module-level guard to avoid duplicate welcome in React StrictMode dev remounts +let hasShownWelcomeOnceInRuntime = false; + +interface ChatMessage { + id: string; + role: 'user' | 'assistant'; + content: string; + isStreaming?: boolean; + tokenCount?: number; +} + +/** + * Create the appropriate LLM provider based on configuration + */ +const createLLMProvider = (): LLMProvider => { + const configManager = ConfigManager.instance(); + const providerType = configManager.get('general.llm_provider') as string; + + switch (providerType) { + case 'claude': + return new ClaudeProvider(); + case 'gemini': + return new GeminiProvider(); + case 'openai_compatible': + case 'openai': + default: + return new OpenAIProvider(); + } +}; + +const ChatBox: React.FC = () => { + const [inputValue, setInputValue] = useState(''); + const textareaRef = useRef(null); + + // Initialize with empty messages + const [messages, setMessages] = useState([]); + const [isProcessing, setIsProcessing] = useState(false); + const [abortController, setAbortController] = useState(null); + const [lastUserMessage, setLastUserMessage] = useState(''); + + // Tool execution state + const [isExecutingTools, setIsExecutingTools] = useState(false); + const [, setToolResults] = useState(''); // placeholder for future display/use + const [, setCurrentToolIndex] = useState(0); // placeholder for future display/use + + // Track if this is the first message (for system prompt logging) + const [isFirstMessage, setIsFirstMessage] = useState(true); + + // Initialize AgentCore with configured provider and register clear UI callback + useEffect(() => { + const initializeProvider = async () => { + const configManager = ConfigManager.instance(); + + // Ensure ConfigManager is initialized + if (!configManager.getIsInitialized()) { + await configManager.initialize(); + } + + const provider = createLLMProvider(); + const agentCore = AgentCore.instance(); + agentCore.setLLMProvider(provider); + + console.log(`Switched to ${provider.name} provider`); + }; + + // Register the UI clear callback for external components to use + registerClearChatUICallback(clearChatUI); + + initializeProvider(); + + // Auto-trigger welcome on first launch (guard against React StrictMode double-invoke only) + (async () => { + try { + if (hasShownWelcomeOnceInRuntime) return; + hasShownWelcomeOnceInRuntime = true; + + const result = await processUserMessage('/welcome'); + if (result.pseudoAssistantResponse) { + const pseudoId = generateMessageId(); + setMessages(prev => [...prev, { + id: pseudoId, + role: 'assistant', + content: result.pseudoAssistantResponse!, + }]); + } + } catch { + // ignore + } + })(); + }, []); + + const generateMessageId = (): string => { + return `msg_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`; + }; + + const handleAbort = () => { + if (abortController) { + abortController.abort(); + setAbortController(null); + + // Use AgentCore to clean up the data model and get the user message content + const agentCore = AgentCore.instance(); + const userMessageContent = agentCore.abortCurrentRequest(); + + // Remove the last user message and assistant message from UI + setMessages(prev => prev.slice(0, -2)); + + // Restore the user's input (use the content from AgentCore if available) + setInputValue(userMessageContent || lastUserMessage); + + setIsProcessing(false); + } + }; + + const clearChatUI = () => { + // Clear UI state + setMessages([]); + + // Reset first message flag so system prompt will be logged again + setIsFirstMessage(true); + }; + + const handleClearCommand = () => { + const { setStatus } = useProjectStore.getState(); + clearChatHistoryAndUI(setStatus); + }; + + + const addToolResultMessage = (toolName: string, success: boolean, result: string) => { + const toolMsgId = generateMessageId(); + const friendlyDisplay = `${success ? '✅' : '❌'} __**${toolName}**__ \n\n └── ${result}`; + + setMessages(prev => [...prev, { + id: toolMsgId, + role: 'user', + content: friendlyDisplay + }]); + }; + + const executeToolsFromResponse = async (response: string): Promise => { + try { + // Check if response contains XML tool invocations + const xmlBlocks = extractXMLFromString(response); + // Consider only actionable tools (exclude think/thinking) + const actionableBlocks = xmlBlocks.filter((block) => { + const match = block.match(/<([a-zA-Z_][a-zA-Z0-9_-]*)/); + const name = match ? match[1].toLowerCase() : ''; + return name !== 'think' && name !== 'thinking'; + }); + + if (actionableBlocks.length === 0) { + // No actionable tools to execute, stop the loop + return false; + } + + // Start tool execution phase + setIsExecutingTools(true); + setToolResults(''); + setCurrentToolIndex(0); + + const { setStatus } = useProjectStore.getState(); + setStatus(`Executing ${actionableBlocks.length} tool(s)...`); + + const executor = XMLToolExecutor.instance(); + let accumulatedResults = ''; + + // Execute tools sequentially with real-time updates + for (let i = 0; i < actionableBlocks.length; i++) { + setCurrentToolIndex(i + 1); + setStatus(`Executing tool ${i + 1} of ${actionableBlocks.length}...`); + + // Determine tool name from XML block + const toolNameMatch = actionableBlocks[i].match(/<([a-zA-Z_][a-zA-Z0-9_-]*)/); + const toolName = toolNameMatch ? toolNameMatch[1] : 'unknown_tool'; + + try { + // Execute single XML block + const results = await executor.executeXMLTools(actionableBlocks[i]); + const result = results[0]; // Single block should give single result + + if (result) { + // Add friendly display message + addToolResultMessage(toolName, result.success, result.result); + + // Accumulate formatted result for LLM (skip thinking tools) + if (toolName !== 'thinking' && toolName !== 'think') { + const formattedResult = `tool: ${toolName}\nsuccess: ${result.success}\nresult:\n${result.result}\n------------\n`; + accumulatedResults += formattedResult; + } + } + } catch (error) { + // Handle individual tool error + addToolResultMessage(toolName, false, `Tool execution failed: ${error}`); + + // Accumulate error result for LLM (skip thinking tools) + if (toolName !== 'thinking' && toolName !== 'think') { + const formattedResult = `tool: ${toolName}\nsuccess: false\nresult:\nTool execution failed: ${error}\n------------\n`; + accumulatedResults += formattedResult; + } + } + } + + // Store accumulated results + setToolResults(accumulatedResults); + setIsExecutingTools(false); + + // Check if agent is still working on task before sending results to LLM + const agentCore = AgentCore.instance(); + const isStillWorkingOnTask = agentCore.getAgentState().getIsWorkingOnTask(); + + if (isStillWorkingOnTask) { + // Send tool results back to LLM + setStatus('Processing tool results...'); + await sendToolResultsToLLM(accumulatedResults); + } else { + // Agent is no longer working on task, ignore results and return control to user + setStatus('Tool execution completed'); + } + + return true; // Tools were found and executed + + } catch (error) { + console.error('Error executing tools:', error); + setIsExecutingTools(false); + const { setStatus } = useProjectStore.getState(); + setStatus(`Tool execution failed: ${error}`); + return false; // Tool execution failed + } + }; + + const sendToolResultsToLLM = async (toolResultsString: string): Promise => { + // Send tool results as hidden user input to LLM + setIsProcessing(true); + + // Create abort controller for this request + const controller = new AbortController(); + setAbortController(controller); + + // Add streaming assistant message for the response + const assistantMsgId = generateMessageId(); + setMessages(prev => [...prev, { + id: assistantMsgId, + role: 'assistant', + content: 'processing... 0 tokens received. click here to abort.', + isStreaming: true, + tokenCount: 0 + }]); + + try { + const agentCore = AgentCore.instance(); + let assistantResponse = ''; + let tokenCount = 0; + + // Log the tool results being sent to LLM + console.log('------------ USER ------------'); + console.log(toolResultsString); + console.log('------------------------------'); + + for await (const chunk of agentCore.processUserInput(toolResultsString)) { + // Check if request was aborted + if (controller.signal.aborted) { + return; + } + + if (chunk.type === 'text') { + assistantResponse += chunk.content; + tokenCount++; + + // Update streaming message with token count and abort link + setMessages(prev => prev.map(msg => + msg.id === assistantMsgId + ? { ...msg, content: `processing... ${tokenCount} tokens received. click here to abort.`, tokenCount } + : msg + )); + } else if (chunk.type === 'done') { + // Replace with final response + setMessages(prev => prev.map(msg => + msg.id === assistantMsgId + ? { ...msg, content: assistantResponse, isStreaming: false, tokenCount: undefined } + : msg + )); + + // Log the complete assistant response + console.log('------------ ASSISTANT ------------'); + console.log(assistantResponse); + console.log('-----------------------------------'); + + // Check if the new response contains more tools + const hasMoreTools = await executeToolsFromResponse(assistantResponse); + + // If no more tools were found, set working flag to false + if (!hasMoreTools) { + const agentCore = AgentCore.instance(); + agentCore.getAgentState().setIsWorkingOnTask(false); + } + + break; + } + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + // Request was aborted, don't show error + return; + } + + console.error('Error processing tool results:', error); + // Update with error message + setMessages(prev => prev.map(msg => + msg.id === assistantMsgId + ? { ...msg, content: 'Error: Failed to process tool results', isStreaming: false, tokenCount: undefined } + : msg + )); + } finally { + setAbortController(null); + setIsProcessing(false); + } + }; + + const handleSend = async () => { + if (inputValue.trim() && !isProcessing) { + const userMessage = inputValue.trim(); + setLastUserMessage(userMessage); + setInputValue(''); + + // Run message through the filter system + const filterResult = await processUserMessage(userMessage); + + // Conditionally show the user message bubble + if (filterResult.displayUserMessage) { + const userMsgId = generateMessageId(); + setMessages(prev => [...prev, { + id: userMsgId, + role: 'user', + content: userMessage + }]); + } + + // If we have a pseudo assistant response, show it immediately + if (filterResult.pseudoAssistantResponse) { + const pseudoId = generateMessageId(); + setMessages(prev => [...prev, { + id: pseudoId, + role: 'assistant', + content: filterResult.pseudoAssistantResponse!, + }]); + } + + // If we shouldn't send anything to LLM, stop here + if (!filterResult.sendToLLM || !filterResult.finalMessageForLLM) { + return; + } + + setIsProcessing(true); + + // Create abort controller for this request + const controller = new AbortController(); + setAbortController(controller); + + // Add streaming assistant message + const assistantMsgId = generateMessageId(); + setMessages(prev => [...prev, { + id: assistantMsgId, + role: 'assistant', + content: 'processing... 0 tokens received. click here to abort.', + isStreaming: true, + tokenCount: 0 + }]); + + try { + const agentCore = AgentCore.instance(); + let assistantResponse = ''; + let tokenCount = 0; + + // Set working on task flag when user sends a message + agentCore.getAgentState().setIsWorkingOnTask(true); + + // Log system prompt only for first message or first message after clear + if (isFirstMessage) { + try { + const systemPrompt = await SystemPrompts.getSystemPromptWithContext(); + console.log('------------ SYSTEM ------------'); + console.log(systemPrompt); + console.log('--------------------------------'); + } catch (error) { + console.error('Failed to log system prompt:', error); + } + // Mark that we've logged the system prompt for this conversation + setIsFirstMessage(false); + } + + // Log the final user message being sent to LLM + console.log('------------ USER ------------'); + console.log(filterResult.finalMessageForLLM); + console.log('------------------------------'); + + for await (const chunk of agentCore.processUserInput(filterResult.finalMessageForLLM)) { + // Check if request was aborted + if (controller.signal.aborted) { + return; + } + + if (chunk.type === 'text') { + assistantResponse += chunk.content; + tokenCount++; + + // Update streaming message with token count and abort link + setMessages(prev => prev.map(msg => + msg.id === assistantMsgId + ? { ...msg, content: `processing... ${tokenCount} tokens received. click here to abort.`, tokenCount } + : msg + )); + } else if (chunk.type === 'done') { + // Replace with final response + setMessages(prev => prev.map(msg => + msg.id === assistantMsgId + ? { ...msg, content: assistantResponse, isStreaming: false, tokenCount: undefined } + : msg + )); + + // Log the complete assistant response + console.log('------------ ASSISTANT ------------'); + console.log(assistantResponse); + console.log('-----------------------------------'); + + // Check if response contains tools to execute + const hasTools = await executeToolsFromResponse(assistantResponse); + + // If no tools were found, set working flag to false and return control to user + if (!hasTools) { + agentCore.getAgentState().setIsWorkingOnTask(false); + } + + break; + } + } + } catch (error) { + if (error instanceof Error && error.name === 'AbortError') { + // Request was aborted, don't show error + return; + } + + console.error('Error processing message:', error); + // Update with error message + setMessages(prev => prev.map(msg => + msg.id === assistantMsgId + ? { ...msg, content: 'Error: Failed to process message', isStreaming: false, tokenCount: undefined } + : msg + )); + } finally { + setAbortController(null); + setIsProcessing(false); + } + } + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + // Allow Shift+Enter for new lines (default textarea behavior) + }; + + const handleInputFocus = () => { + // Set a data attribute on the textarea to help global keyboard handler identify it + if (textareaRef.current) { + textareaRef.current.setAttribute('data-chatbox-input', 'true'); + } + }; + + const handleInputBlur = () => { + // Remove the data attribute when losing focus + if (textareaRef.current) { + textareaRef.current.removeAttribute('data-chatbox-input'); + } + }; + + // Auto-resize textarea based on content + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`; + } + }, [inputValue]); + + // Auto-scroll to bottom when new messages arrive + const messagesEndRef = useRef(null); + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, [messages]); + + // Auto-focus input when it becomes visible (when processing and tool execution complete) + useEffect(() => { + if (!isProcessing && !isExecutingTools && textareaRef.current) { + // Use a small delay to ensure the DOM has updated + setTimeout(() => { + textareaRef.current?.focus(); + // Also scroll to bottom when input becomes visible after tool execution + // This ensures proper scroll position after layout changes from showing input box + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }, 0); + } + }, [isProcessing, isExecutingTools]); + + return ( +
+
+

K.G.Studio Musician Assistant

+
+ {isProcessing && ( + + )} + +
+
+ +
+ {messages.map((message) => ( + message.role === 'user' ? ( + + ) : ( + + ) + ))} +
+
+ + {!isProcessing && !isExecutingTools && ( +
+