├── viewer ├── __init__.py ├── public │ ├── logo.png │ └── favicon.png ├── src │ ├── assets │ │ ├── material-symbols.woff2 │ │ └── main.css │ ├── _router.js │ ├── storage.js │ ├── main.js │ ├── Options.vue │ ├── cache.js │ ├── InputText.vue │ ├── Card.vue │ ├── keynav.js │ ├── CardImage.vue │ ├── CardText.vue │ ├── Selector.vue │ ├── App.vue │ ├── CardVideo.vue │ ├── chartZoomPlugin.js │ ├── store.js │ └── CardFloat.vue ├── jsconfig.json ├── .gitignore ├── index.html ├── config.py ├── __main__.py ├── README.md ├── package.json ├── vite.config.js ├── filesystems.py ├── server.py └── package-lock.json ├── MANIFEST.in ├── requirements.txt ├── .gitignore ├── pyproject.toml ├── scope ├── __init__.py ├── reader.py ├── writer.py └── formats.py ├── .github └── workflows │ └── tests.yaml ├── LICENSE ├── tests ├── test_video.py ├── test_float.py └── test_image.py ├── README.md └── setup.py /viewer/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include README.md 2 | include requirements.txt 3 | -------------------------------------------------------------------------------- /viewer/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danijar/scope/HEAD/viewer/public/logo.png -------------------------------------------------------------------------------- /viewer/public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danijar/scope/HEAD/viewer/public/favicon.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | av 2 | elements>=3.16.6 3 | fastapi 4 | mediapy 5 | numpy 6 | orjson 7 | pillow 8 | uvicorn[standard] 9 | -------------------------------------------------------------------------------- /viewer/src/assets/material-symbols.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danijar/scope/HEAD/viewer/src/assets/material-symbols.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.py[cod] 3 | .pytest_cache 4 | MANIFEST 5 | MUJOCO_LOG.TXT 6 | [';:] 7 | \[ 8 | \] 9 | __pycache__/ 10 | dist 11 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.pytest.ini_options] 2 | markers = ['slow'] 3 | addopts = ['--strict-config', '-ra'] 4 | pythonpath = ['.'] 5 | testpaths = ['tests'] 6 | -------------------------------------------------------------------------------- /viewer/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | } 6 | }, 7 | "exclude": ["node_modules", "dist"] 8 | } 9 | -------------------------------------------------------------------------------- /scope/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.6.3' 2 | 3 | from .reader import Reader 4 | from .writer import Writer 5 | 6 | from .formats import table_read 7 | from .formats import table_append 8 | 9 | from . import formats 10 | -------------------------------------------------------------------------------- /viewer/.gitignore: -------------------------------------------------------------------------------- 1 | *.local 2 | *.log 3 | .DS_Store 4 | /cypress/screenshots/ 5 | /cypress/videos/ 6 | coverage 7 | dist 8 | dist-ssr 9 | lerna-debug.log* 10 | logs 11 | node_modules 12 | npm-debug.log* 13 | pnpm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | -------------------------------------------------------------------------------- /viewer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Scope 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /viewer/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import elements 4 | 5 | 6 | config = elements.Flags( 7 | port=int(os.environ.get('SCOPE_PORT', 8000)), 8 | basedir=os.environ.get('SCOPE_BASEDIR', ''), 9 | filesystem=os.environ.get('SCOPE_FILESYSTEM', 'elements'), 10 | cachedir='/tmp/scope-cache', 11 | cachesize=int(4e9), # 4 GB 12 | maxdepth=2, 13 | workers=32, 14 | debug=False, 15 | ).parse() 16 | 17 | assert config.basedir, config.basedir 18 | -------------------------------------------------------------------------------- /viewer/__main__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import sys 4 | 5 | import uvicorn 6 | 7 | package = str(pathlib.Path(__file__).parent) 8 | sys.path.insert(0, package) 9 | from config import config 10 | 11 | 12 | def main(): 13 | 14 | print(config) 15 | 16 | # Uvicorn watches cwd for changes for reload. 17 | os.chdir(package) 18 | 19 | uvicorn.run( 20 | 'server:app', 21 | host='0.0.0.0', 22 | port=config.port, 23 | reload=config.debug, 24 | workers=None if config.debug else config.workers, 25 | ) 26 | 27 | 28 | if __name__ == '__main__': 29 | main() 30 | -------------------------------------------------------------------------------- /viewer/README.md: -------------------------------------------------------------------------------- 1 | # scope 2 | 3 | This template should help get you started developing with Vue 3 in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). 8 | 9 | ## Customize configuration 10 | 11 | See [Vite Configuration Reference](https://vitejs.dev/config/). 12 | 13 | ## Project Setup 14 | 15 | ```sh 16 | npm install 17 | ``` 18 | 19 | ### Compile and Hot-Reload for Development 20 | 21 | ```sh 22 | npm run dev 23 | ``` 24 | 25 | ### Compile and Minify for Production 26 | 27 | ```sh 28 | npm run build 29 | ``` 30 | -------------------------------------------------------------------------------- /viewer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scope", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "frontend": "vite --host", 8 | "backend": "cd .. && python -m viewer --debug=True", 9 | "dev": "concurrently 'npm run frontend' 'npm run backend'", 10 | "build": "vite build", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "chart.js": "^4.4.4", 15 | "vue": "^3.5.8" 16 | }, 17 | "devDependencies": { 18 | "@vitejs/plugin-vue": "^5.0.5", 19 | "concurrently": "^9.0.1", 20 | "vite": "^5.3.1", 21 | "vite-plugin-vue-devtools": "^7.3.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /viewer/src/_router.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import HomeView from '../views/HomeView.vue' 3 | 4 | const router = createRouter({ 5 | history: createWebHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: '/', 9 | name: 'home', 10 | component: HomeView 11 | }, 12 | { 13 | path: '/about', 14 | name: 'about', 15 | // route level code-splitting 16 | // this generates a separate chunk (About.[hash].js) for this route 17 | // which is lazy-loaded when the route is visited. 18 | component: () => import('../views/AboutView.vue') 19 | } 20 | ] 21 | }) 22 | 23 | export default router 24 | -------------------------------------------------------------------------------- /viewer/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | const serverPort = process.env.SCOPE_PORT || 6000 7 | const clientPort = process.env.SCOPE_PORT_DEV || 8000 8 | 9 | export default defineConfig({ 10 | plugins: [ 11 | vue(), 12 | ], 13 | resolve: { 14 | alias: { 15 | '@': fileURLToPath(new URL('./src', import.meta.url)) 16 | } 17 | }, 18 | server: { 19 | port: clientPort, 20 | proxy: { 21 | '/api': { 22 | target: `http://127.0.0.1:${serverPort}`, 23 | changeOrigin: true, 24 | timeout: 60000, // 1 min 25 | } 26 | } 27 | } 28 | }) 29 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: scope 2 | on: 3 | push: { branches: [main] } 4 | pull_request: { branches: [main] } 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | fail-fast: False 10 | matrix: 11 | python-version: ["3.10", "3.11", "3.12", "3.13"] 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Python 15 | uses: actions/setup-python@v5 16 | with: 17 | python-version: ${{ matrix.python-version }} 18 | - name: Dependencies 19 | run: | 20 | python -m pip install --upgrade pip 21 | python -m pip install -r requirements.txt 22 | python -m pip install pytest 23 | - name: Tests 24 | run: | 25 | python -m pytest tests 26 | - name: Package 27 | run: | 28 | python -m pip install scope 29 | -------------------------------------------------------------------------------- /viewer/src/storage.js: -------------------------------------------------------------------------------- 1 | export function saveStorage(key, value, allowEmpty = false) { 2 | if (key.length == 0) 3 | return fallback 4 | if (value.length === 0 && !allowEmpty) 5 | return 6 | let entry = { type: null, value: value } 7 | if (value instanceof Set) 8 | entry = { type: 'Set', value: [...value] } 9 | console.log('saveStorage', key, entry) 10 | localStorage.setItem(key, JSON.stringify(entry)) 11 | } 12 | 13 | export function loadStorage(key, fallback) { 14 | if (key.length == 0) 15 | return fallback 16 | const entry = JSON.parse(localStorage.getItem(key)) 17 | if (entry === null) 18 | return fallback 19 | if (entry.type === 'Set') 20 | return new Set(entry.value) 21 | else if (entry.type === null) 22 | return entry.value 23 | else 24 | throw `invalid storage entry: ${key} ${entry}` 25 | } 26 | -------------------------------------------------------------------------------- /viewer/src/main.js: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | 3 | import { createApp } from 'vue' 4 | import App from './App.vue' 5 | 6 | import * as chart from 'chart.js' 7 | import { ZoomPlugin } from './chartZoomPlugin.js' 8 | 9 | chart.Chart.register( 10 | chart.LineController, 11 | chart.ScatterController, 12 | chart.BarController, 13 | chart.LineElement, 14 | chart.PointElement, 15 | chart.BarElement, 16 | chart.LinearScale, 17 | chart.LogarithmicScale, 18 | chart.Title, 19 | chart.CategoryScale, 20 | chart.Tooltip, 21 | ZoomPlugin, 22 | ) 23 | 24 | const app = createApp(App) 25 | 26 | // app.config.errorHandler = (err, vm, info) => { 27 | // console.error('Error:', err) 28 | // console.error('Vue component:', vm) 29 | // console.error('Additional info:', info) 30 | // }; 31 | 32 | app.mount('body') 33 | 34 | document.fonts.load('24px "Material Symbols Outlined"').then(results => { 35 | if (results.length) 36 | document.body.classList.add('icon-font-loaded') 37 | else 38 | console.error('Failed to load icon font') 39 | }) 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Danijar Hafner 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /scope/reader.py: -------------------------------------------------------------------------------- 1 | import re 2 | import pathlib 3 | 4 | from . import formats 5 | 6 | 7 | FORMATS = [ 8 | formats.Text(), 9 | formats.Float(), 10 | formats.Image(), 11 | formats.Video(), 12 | ] 13 | 14 | 15 | class Reader: 16 | 17 | def __init__(self, logdir, formats=None): 18 | formats = formats or FORMATS 19 | if isinstance(logdir, str): 20 | logdir = pathlib.Path(logdir) 21 | self.logdir = logdir / 'scope' 22 | self.fmts = {x.extension: x for x in formats} 23 | self.cols = {} 24 | for child in sorted(self.logdir.glob('*')): 25 | basename, ext = child.name.rsplit('.', 1) 26 | key = basename.replace('-', '/') 27 | assert re.match(r'[a-z0-9_]+(/[a-z0-9_]+)?', key), key 28 | self.cols[key] = (child.name, self.fmts[ext]) 29 | 30 | def keys(self): 31 | return tuple(self.cols.keys()) 32 | 33 | def __getitem__(self, key): 34 | name, fmt = self.cols[key] 35 | return fmt.read(self.logdir / name) 36 | 37 | def length(self, key): 38 | name, fmt = self.cols[key] 39 | return fmt.length(self.logdir / name) 40 | 41 | def load(self, key, filename): 42 | _, fmt = self.cols[key] 43 | buffer = (self.logdir / filename).read_bytes() 44 | value = fmt.decode(buffer) 45 | return value 46 | -------------------------------------------------------------------------------- /tests/test_video.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import numpy as np 4 | import pytest 5 | import scope 6 | 7 | 8 | class TestVideo: 9 | 10 | @pytest.mark.parametrize('channels', (3, 1)) 11 | @pytest.mark.parametrize('fmt', ( 12 | scope.formats.Video(fps=10), 13 | )) 14 | def test_roundtrip(self, tmpdir, fmt, channels): 15 | logdir = pathlib.Path(tmpdir) 16 | writer = scope.Writer(logdir, workers=0, formats=[fmt]) 17 | vid1 = np.ones((5, 64, 128, channels), np.uint8) + 12 18 | vid2 = np.ones((5, 64, 128, channels), np.uint8) + 255 19 | writer.add(0, {'foo': vid1}) 20 | writer.add(5, {'foo': vid2}) 21 | writer.flush() 22 | names = {x.name for x in (logdir / 'scope').glob('*')} 23 | assert len(names) == 1 24 | name = list(names)[0] 25 | assert name in ('foo.mp4', 'foo.webm') 26 | assert (logdir / 'scope' / name / 'index').stat().st_size == (8 + 8) * 2 27 | assert len(list((logdir / 'scope' / name).glob('*'))) == 1 + 2 28 | reader = scope.Reader(logdir) 29 | assert reader.keys() == ('foo',) 30 | assert reader.length('foo') == 2 31 | steps, filenames = reader['foo'] 32 | assert (steps == np.array([0, 5])).all() 33 | values = [reader.load('foo', x) for x in filenames] 34 | assert all(x.dtype == np.uint8 for x in values) 35 | assert np.allclose(values, [vid1, vid2], rtol=0.1, atol=3) 36 | -------------------------------------------------------------------------------- /viewer/src/Options.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | 35 | 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🔬 Scope 2 | 3 | Scalable metrics logging and analysis. 4 | 5 | ## Features 6 | 7 | - 🚀 **Scalable:** Quickly log and view petabytes of metrics, thousands of keys, and large videos. 8 | - 🎞️ **Formats:** Log and view scalars, text, images, and videos. Easy to extend with custom formats. 9 | - 🧑🏻‍🔬 **Productivity:** Metrics viewer with focus on power users with full keyboard support. 10 | - ☁️ **Cloud support:** Directly write to and read from Cloud storage via pathlib interface. 11 | - 🍃 **Lightweight:** The writer and reader measure only ~400 lines of Python code. 12 | - 🧱 **Reliable:** Unit tested and used across diverse research projects. 13 | 14 | ## Usage 15 | 16 | ### Installation 17 | 18 | ```sh 19 | pip install scope 20 | ``` 21 | 22 | ### Writing 23 | 24 | ```python 25 | import scope 26 | 27 | writer = scope.Writer(logdir) 28 | 29 | for step in range(3): 30 | writer.add(step, { 31 | 'foo': 42, 32 | 'bar': np.zeros((100, 640, 360, 3), np.uint8), 33 | 'baz': 'Hello World', 34 | }) 35 | writer.flush() 36 | ``` 37 | 38 | ### Viewing 39 | 40 | ```sh 41 | python -m scope.viewer --basedir ... --port 8000 42 | ``` 43 | 44 | ### Reading 45 | 46 | ```python 47 | import scope 48 | 49 | reader = scope.Reader(logdir) 50 | print(reader.keys()) # ('foo', 'bar', 'baz') 51 | 52 | print(reader.length('foo')) # 3 53 | steps, values = reader['foo'] 54 | print(steps) # np.array([0, 1, 2], np.int64) 55 | print(values) # np.array([42, 42, 42], np.float64) 56 | 57 | steps, filenames = reader['bar'] 58 | reader.load('bar', filenames[-1]) # np.zeros((100, 640, 360, 3), np.uint8) 59 | ``` 60 | -------------------------------------------------------------------------------- /viewer/src/cache.js: -------------------------------------------------------------------------------- 1 | import { ref, watch, computed, shallowRef } from 'vue' 2 | 3 | 4 | export function reactiveCache(func) { 5 | 6 | const cache = ref({}) 7 | const loading = ref({}) 8 | 9 | function add(key, refresh = false) { 10 | if ((key in cache.value || key in loading.value) && !refresh) 11 | return 12 | cancel(key) 13 | const result = func(key) 14 | if (result instanceof Promise) { 15 | result.canceled = false 16 | loading.value[key] = value 17 | result.then(value => { if (!result.canceled) cache.value[key] = shallowRef(value) }) 18 | result.finally(() => { if (!result.canceled) loading.value.delete(key) }) 19 | } else { 20 | cache.value[key] = shallowRef(result) 21 | } 22 | } 23 | 24 | function remove(key) { 25 | cancel(key) 26 | if (key in cache.value) 27 | delete cache.value[key] 28 | } 29 | 30 | function setTo(keys, refresh = false) { 31 | const requested = new Set([...keys]) 32 | const existing = [...Object.keys(cache.value), ...Object.keys(loading.value)] 33 | for (const key of existing) 34 | if (!requested.has(key)) 35 | remove(key) 36 | for (const key of keys) 37 | add(key, refresh) 38 | } 39 | 40 | function refresh() { 41 | const existing = [...Object.keys(cache.value), ...Object.keys(loading.value)] 42 | for (const key of existing) 43 | add(key, true) 44 | } 45 | 46 | function cancel(key) { 47 | if (!(key in loading.value)) 48 | return 49 | loading.value[key].canceled = true 50 | delete loading.value[key] 51 | } 52 | 53 | cache.loading = loading 54 | cache.add = add 55 | cache.remove = remove 56 | cache.setTo = setTo 57 | cache.refresh = refresh 58 | cache.cancel = cancel 59 | 60 | return cache 61 | } 62 | -------------------------------------------------------------------------------- /viewer/src/InputText.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 37 | 38 | 64 | 65 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pathlib 3 | import re 4 | import subprocess 5 | 6 | import setuptools 7 | from distutils.command.sdist import sdist as _sdist 8 | from setuptools.command.develop import develop as _develop 9 | 10 | 11 | def parse_requirements(filename): 12 | requirements = pathlib.Path(filename) 13 | requirements = requirements.read_text().split('\n') 14 | requirements = [x for x in requirements if x.strip()] 15 | return requirements 16 | 17 | 18 | def parse_version(filename): 19 | text = (pathlib.Path(__file__).parent / filename).read_text() 20 | version = re.search(r"__version__ = '(.*)'", text).group(1) 21 | return version 22 | 23 | 24 | def build_viewer(): 25 | orig = os.getcwd() 26 | try: 27 | os.chdir(pathlib.Path(__file__).parent / 'viewer') 28 | subprocess.check_call(['npm', 'install']) 29 | subprocess.check_call(['npm', 'run', 'build']) 30 | finally: 31 | os.chdir(orig) 32 | 33 | 34 | def patch(cmd): 35 | class Patched(cmd): 36 | def run(self): 37 | build_viewer() 38 | cmd.run(self) 39 | return Patched 40 | 41 | 42 | setuptools.setup( 43 | name='scope', 44 | version=parse_version('scope/__init__.py'), 45 | description='Metrics logging and analysis', 46 | url='http://github.com/danijar/scope', 47 | long_description=pathlib.Path('README.md').read_text(), 48 | long_description_content_type='text/markdown', 49 | install_requires=parse_requirements('requirements.txt'), 50 | packages=['scope', 'scope.viewer'], 51 | package_dir={ 52 | 'scope': 'scope', 53 | 'scope.viewer': 'viewer', 54 | }, 55 | package_data={ 56 | 'scope.viewer': ['dist/**/*'], 57 | }, 58 | include_package_data=True, 59 | cmdclass={ 60 | 'sdist': patch(_sdist), 61 | 'develop': patch(_develop), 62 | }, 63 | classifiers=[ 64 | 'Intended Audience :: Science/Research', 65 | 'License :: OSI Approved :: MIT License', 66 | 'Programming Language :: Python :: 3', 67 | ], 68 | ) 69 | -------------------------------------------------------------------------------- /viewer/src/Card.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 43 | 44 | 65 | -------------------------------------------------------------------------------- /tests/test_float.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import scope 4 | import numpy as np 5 | 6 | 7 | class TestFloat: 8 | 9 | def test_roundtrip(self, tmpdir): 10 | logdir = pathlib.Path(tmpdir) 11 | writer = scope.Writer(logdir, workers=0) 12 | writer.add(0, {'foo': 12}) 13 | writer.add(5, {'foo': 42, 'bar': np.float64(np.pi)}) 14 | writer.flush() 15 | filenames = (logdir / 'scope').glob('*') 16 | assert {x.name for x in filenames} == {'foo.float', 'bar.float'} 17 | assert (logdir / 'scope/foo.float').stat().st_size == (8 + 8) * 2 18 | assert (logdir / 'scope/bar.float').stat().st_size == (8 + 8) * 1 19 | reader = scope.Reader(logdir) 20 | assert reader.keys() == tuple(sorted(['foo', 'bar'])) 21 | assert reader.length('foo') == 2 22 | assert reader.length('bar') == 1 23 | assert equal(reader['foo'], ([0, 5], [12, 42]), (np.int64, np.float64)) 24 | assert equal(reader['bar'], ([5], [np.pi]), (np.int64, np.float64)) 25 | 26 | def test_workers(self, tmpdir): 27 | logdir = pathlib.Path(tmpdir) 28 | writer = scope.Writer(logdir, workers=8) 29 | for step in range(10): 30 | writer.add(step, {'foo': step, 'bar': step}) 31 | writer.flush() 32 | writer.flush() # Block until previous flush is done. 33 | filenames = (logdir / 'scope').glob('*') 34 | assert {x.name for x in filenames} == {'foo.float', 'bar.float'} 35 | assert (logdir / 'scope/foo.float').stat().st_size == (8 + 8) * 10 36 | assert (logdir / 'scope/bar.float').stat().st_size == (8 + 8) * 10 37 | reader = scope.Reader(logdir) 38 | assert equal(reader['foo'], (np.arange(10), np.arange(10))) 39 | assert equal(reader['bar'], (np.arange(10), np.arange(10))) 40 | 41 | def test_namescopes(self, tmpdir): 42 | logdir = pathlib.Path(tmpdir) 43 | writer = scope.Writer(logdir, workers=0) 44 | writer.add(0, {'foo/bar': 12}) 45 | writer.flush() 46 | filenames = (logdir / 'scope').glob('*') 47 | assert {x.name for x in filenames} == {'foo-bar.float'} 48 | reader = scope.Reader(logdir) 49 | assert reader.keys() == ('foo/bar',) 50 | assert reader.length('foo/bar') == 1 51 | assert equal(reader['foo/bar'], ([0], [12]), (np.int64, np.float64)) 52 | 53 | 54 | def equal(actuals, references, dtypes=None): 55 | dtypes = dtypes or [x.dtype for x in actuals] 56 | assert len(actuals) == len(references) == len(dtypes) 57 | references = [np.asarray(x, d) for x, d in zip(actuals, dtypes)] 58 | return all((x == y).all() for x, y in zip(actuals, references)) 59 | -------------------------------------------------------------------------------- /tests/test_image.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | 3 | import scope 4 | import numpy as np 5 | 6 | 7 | class TestImage: 8 | 9 | def test_roundtrip(self, tmpdir): 10 | logdir = pathlib.Path(tmpdir) 11 | writer = scope.Writer(logdir, workers=0) 12 | img1 = np.ones((64, 128, 3), np.uint8) + 12 13 | img2 = np.ones((64, 128, 3), np.uint8) + 255 14 | writer.add(0, {'foo': img1}) 15 | writer.add(5, {'foo': img2}) 16 | writer.flush() 17 | assert {x.name for x in (logdir / 'scope').glob('*')} == {'foo.png'} 18 | assert (logdir / 'scope/foo.png/index').stat().st_size == (8 + 8) * 2 19 | assert len(list((logdir / 'scope/foo.png').glob('*'))) == 1 + 2 20 | reader = scope.Reader(logdir) 21 | assert reader.keys() == ('foo',) 22 | assert reader.length('foo') == 2 23 | steps, filenames = reader['foo'] 24 | values = [reader.load('foo', x) for x in filenames] 25 | assert (steps == np.array([0, 5])).all() 26 | assert (np.array(values) == np.array([img1, img2])).all() 27 | 28 | def test_workers(self, tmpdir): 29 | logdir = pathlib.Path(tmpdir) 30 | writer = scope.Writer(logdir, workers=8) 31 | for step in range(5): 32 | for key in ('foo', 'bar', 'baz'): 33 | writer.add(step, {key: np.full((64, 128, 3), step, np.uint8)}) 34 | writer.flush() 35 | writer.flush() # Block until previous flush is done. 36 | assert {x.name for x in (logdir / 'scope').glob('*')} == { 37 | 'foo.png', 'bar.png', 'baz.png'} 38 | for key in ('foo', 'bar', 'baz'): 39 | assert (logdir / f'scope/{key}.png/index').stat().st_size == (8 + 8) * 5 40 | assert len(list((logdir / f'scope/{key}.png').glob('*'))) == 1 + 5 41 | reader = scope.Reader(logdir) 42 | assert reader.keys() == tuple(sorted(['foo', 'bar', 'baz'])) 43 | for key in ('foo', 'bar', 'baz'): 44 | assert reader.length(key) == 5 45 | steps, filenames = reader[key] 46 | values = [reader.load(key, x) for x in filenames] 47 | assert (steps == np.arange(5)).all() 48 | assert all(x.dtype == np.uint8 for x in values) 49 | reference = np.arange(5, dtype=np.uint8)[:, None, None, None] 50 | assert (np.array(values) == reference).all() 51 | 52 | def test_namescopes(self, tmpdir): 53 | img = np.ones((64, 128, 3), np.uint8) + 12 54 | logdir = pathlib.Path(tmpdir) 55 | writer = scope.Writer(logdir, workers=0) 56 | writer.add(0, {'foo/bar': img}) 57 | writer.flush() 58 | assert {x.name for x in (logdir / 'scope').glob('*')} == {'foo-bar.png'} 59 | assert len(list((logdir / 'scope/foo-bar.png').glob('*'))) == 1 + 1 60 | reader = scope.Reader(logdir) 61 | assert reader.keys() == ('foo/bar',) 62 | assert reader.length('foo/bar') == 1 63 | _, filenames = reader['foo/bar'] 64 | assert len(filenames) == 1 65 | assert (reader.load('foo/bar', filenames[0]) == img).all() 66 | -------------------------------------------------------------------------------- /viewer/src/keynav.js: -------------------------------------------------------------------------------- 1 | 2 | const navKeys = ['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight', 'h', 'j', 'k', 'l', 'g', 'G'] 3 | const navKeysInput = ['ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'] 4 | const optionsSelInclude = '.focusable, input, button' 5 | const optionsSelExclude = '.nofocusgroup, .nofocusgroup *' 6 | 7 | let lastG = null 8 | 9 | export function handleKeydown(e) { 10 | 11 | if (e.key == 'Tab') { 12 | setupFocusGroups() 13 | return 14 | } 15 | 16 | if (e.ctrlKey || e.metaKey || e.altKey) 17 | return 18 | if (e.target.nodeName == 'INPUT' && !navKeysInput.includes(e.key)) 19 | return 20 | if (!navKeys.includes(e.key)) 21 | return 22 | 23 | // console.log(e) 24 | 25 | const group = e.target.closest('.focusgroup') 26 | if (!group) return 27 | 28 | const options = getOptions(group) 29 | let index = options.indexOf(e.target) 30 | if (index < 0) return 31 | 32 | const columns = group.getAttribute('focusCols') 33 | 34 | if (e.key == 'g' || e.key == 'G') { 35 | 36 | if (e.shiftKey) { 37 | index = options.length - 1 38 | } else if (Date.now() - lastG < 200) { 39 | index = 0 40 | lastG = null; 41 | } else { 42 | lastG = Date.now(); 43 | } 44 | 45 | } else if (columns) { 46 | let row = Math.floor(index / columns) 47 | let col = index % columns 48 | 49 | if (e.key == 'ArrowDown' || e.key == 'j') 50 | row++ 51 | if (e.key == 'ArrowUp' || e.key == 'k') 52 | row-- 53 | if (e.key == 'ArrowRight' || e.key == 'l') 54 | col++ 55 | if (e.key == 'ArrowLeft' || e.key == 'h') 56 | col-- 57 | 58 | row = Math.max(0, Math.min(row, Math.ceil(options.length / columns) - 1)) 59 | col = Math.max(0, Math.min(col, columns - 1)) 60 | index = row * columns + col 61 | 62 | } else { 63 | 64 | if (e.key == 'ArrowDown' || e.key == 'j') 65 | index++ 66 | if (e.key == 'ArrowUp' || e.key == 'k') 67 | index-- 68 | if (e.key == 'ArrowRight' || e.key == 'l') 69 | index++ 70 | if (e.key == 'ArrowLeft' || e.key == 'h') 71 | index-- 72 | 73 | } 74 | 75 | index = Math.max(0, Math.min(index, options.length - 1)) 76 | options.map((el, i) => el.tabIndex = (i == index) ? 0 : -1) 77 | options[index].focus() 78 | } 79 | 80 | function setupFocusGroups() { 81 | const groups = [...document.querySelectorAll('.focusgroup')] 82 | groups.map(group => { 83 | const options = getOptions(group) 84 | const active = options.filter(el => el.tabIndex == 0) 85 | const index = active.length ? options.indexOf(active[0]) : 0 86 | options.map((el, i) => el.tabIndex = (i == index) ? 0 : -1) 87 | // console.log(group, active, index, options) 88 | }) 89 | } 90 | 91 | function getOptions(group) { 92 | return [...group.querySelectorAll(optionsSelInclude)] 93 | .filter(el => !el.matches(optionsSelExclude)) 94 | } 95 | -------------------------------------------------------------------------------- /scope/writer.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | import dataclasses 3 | import pathlib 4 | import re 5 | 6 | import numpy as np 7 | 8 | from . import formats 9 | 10 | 11 | FORMATS = [ 12 | formats.Text(), 13 | formats.Float(), 14 | formats.Image(), 15 | formats.Video(fps=10), 16 | ] 17 | 18 | 19 | @dataclasses.dataclass 20 | class Column: 21 | 22 | fmt: str 23 | name: str 24 | created: bool 25 | steps: list 26 | values: list 27 | 28 | 29 | class Writer: 30 | 31 | def __init__(self, logdir, workers=8, formats=None): 32 | formats = formats or FORMATS 33 | if isinstance(logdir, str): 34 | logdir = pathlib.Path(logdir) 35 | self.logdir = logdir / 'scope' 36 | self.logdir.mkdir(parents=True, exist_ok=True) 37 | self.workers = workers 38 | self.rng = np.random.default_rng(seed=None) 39 | self.fmts = formats 40 | self.cols = {} 41 | if workers: 42 | self.pool = concurrent.futures.ThreadPoolExecutor(workers, 'scope') 43 | self.futures = [] 44 | 45 | def add(self, step, *args, **kwargs): 46 | assert isinstance(step, (int, np.integer)), type(step) 47 | step = int(step) 48 | mapping = dict(*args, **kwargs) 49 | for key, value in mapping.items(): 50 | if key not in self.cols: 51 | assert re.match(r'[a-z0-9_]+(/[a-z0-9_]+)?', key), key 52 | for fmt in self.fmts: 53 | if fmt.valid(value): 54 | break 55 | else: 56 | raise NotImplementedError( 57 | f"No format supports key '{key}' with {self._info(value)}") 58 | name = key.replace('/', '-') + '.' + fmt.extension 59 | self.cols[key] = Column(fmt, name, False, [], []) 60 | col = self.cols[key] 61 | if not col.fmt.valid(value): 62 | raise ValueError( 63 | f"Key '{key}' contains invalid value {self._info(value)}") 64 | value = col.fmt.convert(value) 65 | col.steps.append(step) 66 | col.values.append(value) 67 | 68 | def flush(self): 69 | if self.workers: 70 | list(self.futures) 71 | jobs = [(c, c.steps, c.values) for c in self.cols.values() if c.steps] 72 | self.futures = self.pool.map(self._write, *zip(*jobs)) 73 | else: 74 | for col in self.cols.values(): 75 | if col.steps: 76 | self._write(col, col.steps, col.values) 77 | for col in self.cols.values(): 78 | col.steps = [] 79 | col.values = [] 80 | 81 | def _write(self, col, steps, values): 82 | try: 83 | path = self.logdir / col.name 84 | if not col.created: 85 | col.fmt.create(path) 86 | col.created = True 87 | col.fmt.write(path, steps, values) 88 | except Exception: 89 | print(f"Exception writing '{col.name}' column") 90 | raise 91 | 92 | def _info(self, value): 93 | if hasattr(value, 'dtype') and hasattr(value, 'shape'): 94 | return f"dtype '{value.dtype}' and shape '{value.shape}'" 95 | return f"type '{type(value)}'" 96 | -------------------------------------------------------------------------------- /viewer/src/CardImage.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 87 | 88 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /viewer/src/CardText.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 85 | 86 | 97 | 98 | -------------------------------------------------------------------------------- /viewer/filesystems.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import pathlib 4 | import subprocess 5 | 6 | import elements 7 | 8 | 9 | class Local: 10 | 11 | def list(self, path): 12 | return os.listdir(path) 13 | 14 | def size(self, path): 15 | return os.path.getsize(path) 16 | 17 | def read(self, path): 18 | with open(path, 'rb') as f: 19 | return f.read() 20 | 21 | def open(self, path, seek=0, limit=None): 22 | f = open(path, 'rb') 23 | f.seek(seek) 24 | return f 25 | 26 | 27 | class Elements: 28 | 29 | def list(self, path): 30 | paths = elements.Path(path).glob('*') 31 | return [str(x) for x in paths] 32 | 33 | def size(self, path): 34 | return elements.Path(path).size 35 | 36 | def read(self, path): 37 | return elements.Path(path).read_bytes() 38 | 39 | def open(self, path, seek=0, limit=None): 40 | f = elements.Path(path).open('rb') 41 | f.seek(seek) 42 | return f 43 | 44 | 45 | class Fileutil: 46 | 47 | def __init__( 48 | self, 49 | ls='fileutil ls {}', 50 | cat='fileutil cat {}', 51 | size="fileutil ls -l {} | tr -s ' ' | cut -d' ' -f5", 52 | catrange='fileutil cat -input_startpos={} -input_endpos={} {}', 53 | ): 54 | self._ls = ls 55 | self._cat = cat 56 | self._size = size 57 | self._catrange = catrange 58 | 59 | def list(self, path): 60 | try: 61 | output = self._sh(self._ls.format(path)) 62 | return [x.rstrip('/') for x in output.decode('utf-8').splitlines()] 63 | except RuntimeError as e: 64 | print(e) 65 | return [] 66 | 67 | def size(self, path): 68 | output = self._sh(self._size.format(path)) 69 | return int(output.decode('utf-8').strip('\n')) 70 | 71 | def read(self, path): 72 | return self._sh(self._cat.format(path)) 73 | 74 | def open(self, path, seek=0, limit=None): 75 | limit = limit or self._size(path) 76 | buffer = self._sh(self._catrange.format(seek, limit, path)) 77 | return io.BytesIO(buffer) 78 | 79 | def _sh(self, cmd): 80 | if '|' in cmd: 81 | process = subprocess.Popen( 82 | cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 83 | out, err = process.communicate() 84 | if process.returncode: 85 | raise RuntimeError((process.returncode, out, err)) 86 | return out 87 | else: 88 | try: 89 | return subprocess.check_output(cmd.split(), shell=False) 90 | except subprocess.CalledProcessError as e: 91 | raise RuntimeError(f'Error in subprocess: {e}') 92 | 93 | 94 | class WithFileCache: 95 | 96 | def __init__(self, fs, cachedir, maxsize): 97 | # Cache size can be exceeded if multiple workers download in parallel. 98 | assert not isinstance(fs, Local) 99 | if isinstance(cachedir, str): 100 | cachedir = pathlib.Path(cachedir) 101 | cachedir.mkdir(exist_ok=True, parents=True) 102 | self.fs = fs 103 | self.localfs = Local() 104 | self.cachedir = cachedir 105 | self.maxsize = maxsize 106 | 107 | def list(self, path): 108 | return self.fs.list(path) 109 | 110 | def size(self, path): 111 | localpath = self._getfile(path) 112 | return self.localfs.size(localpath) 113 | 114 | def read(self, path): 115 | localpath = self._getfile(path) 116 | return self.localfs.read(localpath) 117 | 118 | def open(self, path, seek=0, limit=None): 119 | localpath = self._getfile(path) 120 | return self.localfs.open(localpath, seek, limit) 121 | 122 | def _getfile(self, path): 123 | name = str(path).replace(':', '').replace('//', '/').replace('/', ':') 124 | localpath = self.cachedir / name 125 | if not localpath.exists(): 126 | buffer = self.fs.read(path) 127 | self._freeup(len(buffer), path) 128 | localpath.write_bytes(buffer) 129 | return localpath 130 | 131 | def _freeup(self, needed, path): 132 | # Catch errors because parallel workers share the cache. 133 | pairs = [] 134 | for path in self.cachedir.glob('*'): 135 | try: 136 | stat = path.stat() 137 | pairs.append((path, stat)) 138 | except FileNotFoundError: 139 | pass 140 | pairs = sorted(pairs, key=lambda x: x[1].st_ctime) 141 | while sum([s.st_size for p, s in pairs]) + needed > self.maxsize: 142 | path, _ = pairs.pop(0) 143 | try: 144 | path.unlink() 145 | except IndexError: 146 | pass 147 | -------------------------------------------------------------------------------- /viewer/src/Selector.vue: -------------------------------------------------------------------------------- 1 | 90 | 91 | 119 | 120 | 138 | -------------------------------------------------------------------------------- /viewer/src/assets/main.css: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | * Normalization 3 | *****************************************************************************/ 4 | 5 | *, *::before, *::after { 6 | box-sizing: border-box; 7 | font-weight: normal; 8 | margin: 0; 9 | } 10 | 11 | label, input, button { 12 | font-size: 1rem; 13 | min-width: 0; 14 | border: none; 15 | background: none; 16 | text-align: left; 17 | line-height: 1; 18 | } 19 | 20 | html, body { 21 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 22 | Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 23 | sans-serif; 24 | font-size: 14px; 25 | text-rendering: optimizeLegibility; 26 | -webkit-font-smoothing: antialiased; 27 | -moz-osx-font-smoothing: grayscale; 28 | } 29 | 30 | /****************************************************************************** 31 | * Layout 32 | *****************************************************************************/ 33 | 34 | .layoutCol { display: flex; overflow: hidden; flex-direction: column; } 35 | .layoutRow { display: flex; overflow: hidden; flex-direction: row; } 36 | .fill { flex: 1 1; } 37 | 38 | /****************************************************************************** 39 | * Theme 40 | *****************************************************************************/ 41 | 42 | .app { 43 | --fg1: #000; 44 | --fg2: #333; 45 | --fg3: #999; 46 | --bg1: #fff; 47 | --bg2: #f5f5f5; 48 | --bg3: #eee; 49 | --br: #ddd; 50 | --focus: #00aaff; 51 | } 52 | 53 | .app.dark { 54 | color-scheme: dark; 55 | --fg1: #eee; 56 | --fg2: #ddd; 57 | --fg3: #aaa; 58 | --bg1: #333; 59 | --bg2: #222; 60 | --bg3: #111; 61 | --br: #444; 62 | --focus: #00aaff; 63 | } 64 | 65 | .app { background: var(--bg1); } 66 | .dark .logo { filter: invert(); } 67 | 68 | * { scrollbar-color: rgba(127, 127, 127, 0.3) transparent; } 69 | 70 | h2 { font-size: 1.4rem; font-weight: 500; color: var(--fg1); } 71 | input { font-family: monospace; } 72 | 73 | /****************************************************************************** 74 | * Icons 75 | *****************************************************************************/ 76 | 77 | @font-face { 78 | font-family: 'Material Symbols Outlined'; 79 | font-style: normal; 80 | font-weight: 400; 81 | src: url(@/assets/material-symbols.woff2) format('woff2'); 82 | } 83 | 84 | .icon { 85 | font-family: 'Material Symbols Outlined'; 86 | font-weight: normal; 87 | font-style: normal; 88 | font-size: 24px; 89 | line-height: 1; 90 | letter-spacing: normal; 91 | text-transform: none; 92 | display: inline-block; 93 | white-space: nowrap; 94 | word-wrap: normal; 95 | direction: ltr; 96 | -webkit-font-feature-settings: 'liga'; 97 | -webkit-font-smoothing: antialiased; 98 | user-select: none; 99 | vertical-align: middle; 100 | } 101 | 102 | .icon.filled { font-variation-settings: 'FILL' 1; } 103 | 104 | .icon { opacity: 0; } 105 | .icon-font-loaded .icon { opacity: inherit; } 106 | 107 | /****************************************************************************** 108 | * Buttons 109 | *****************************************************************************/ 110 | 111 | .btn { 112 | flex: 0 0 content; 113 | display: inline-block; 114 | cursor: pointer; 115 | padding: .4rem; 116 | border-radius: .5rem; 117 | color: var(--fg2); 118 | border: none; 119 | background-color: transparent; 120 | } 121 | .btn:hover, .btn:focus { background-color: var(--bg2); outline: none; } 122 | .btn:active { background: var(--bg3); } 123 | .btn + .btn { margin-left: .1rem; } 124 | .btn, .smooth { transition: background-color .1s linear, color .1s linear; } 125 | 126 | /****************************************************************************** 127 | * Keyboard 128 | *****************************************************************************/ 129 | 130 | input:focus, button:focus, :focus-visible { outline: none; } 131 | 132 | .keyboardMode .btn:focus, 133 | .keyboardMode .focusOutline:focus, 134 | .keyboardMode .focusOutline:focus-visible, 135 | .keyboardMode .focusOutline:focus-within { 136 | outline: 2px solid var(--focus) !important; 137 | outline-offset: -2px !important; 138 | } 139 | 140 | .keyboardMode * { transition: none; } 141 | 142 | /* *[tabindex="0"] { background: green !important; } */ 143 | /* *[tabindex="-1"] { background: red !important; } */ 144 | 145 | /****************************************************************************** 146 | * Animations 147 | *****************************************************************************/ 148 | 149 | .v-enter-active, .v-leave-active { transition: opacity .5s ease !important; } 150 | .v-enter-from, .v-leave-to { opacity: 0 !important; } 151 | 152 | .spinner { 153 | color: var(--fg3); 154 | height: 1em; width: 1em; 155 | animation: spinner 1.5s infinite linear running; 156 | } 157 | @keyframes spinner { 158 | from { transform:rotate(0deg); } 159 | to { transform:rotate(360deg); } 160 | } 161 | -------------------------------------------------------------------------------- /viewer/src/App.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 109 | 110 | 138 | -------------------------------------------------------------------------------- /viewer/src/CardVideo.vue: -------------------------------------------------------------------------------- 1 | 130 | 131 | 164 | 165 | 181 | 182 | -------------------------------------------------------------------------------- /viewer/src/chartZoomPlugin.js: -------------------------------------------------------------------------------- 1 | import { getRelativePosition } from 'chart.js/helpers' 2 | 3 | export const ZoomPlugin = { 4 | 5 | id: 'zoom', 6 | 7 | events: ['mousemove', 'mousedown', 'mouseup', 'mouseout'], 8 | 9 | afterInit: function(chart, args, options) { 10 | chart.zoom = { 11 | options: options, 12 | canvasPos: null, 13 | dataPos: null, 14 | dragStart: null, 15 | dragging: false, 16 | updating: false, 17 | prevMouseUp: 0, 18 | prevMouseDown: 0, 19 | mouseUpListener: null, 20 | } 21 | for (const event of this.events) 22 | if (chart.options.events.indexOf(event) < 0) 23 | chart.options.events.push(event) 24 | chart.zoom.mouseUpListener = (e) => { this.stopDrag(chart) } 25 | chart.zoom.moveDebounced = debounce((e) => { 26 | this.move(chart, getRelativePosition(e, chart)) }, 3, true) 27 | document.addEventListener('mouseup', chart.zoom.mouseUpListener) 28 | }, 29 | 30 | afterDestroy: function(chart) { 31 | document.removeEventListener('mouseup', chart.zoom.mouseUpListener) 32 | }, 33 | 34 | afterEvent: function(chart, args) { 35 | 36 | // console.log('after event', args.event.type) 37 | 38 | // if (args.event.type != 'mousemove') 39 | // console.log(args.event.type) 40 | 41 | if (args.event.type == 'mousemove') { 42 | // this.move(chart, getRelativePosition(args.event, chart)) 43 | chart.zoom.moveDebounced(args.event) 44 | } 45 | 46 | if (args.event.type == 'mousedown') { 47 | this.move(chart, getRelativePosition(args.event, chart)) 48 | const now = Date.now() 49 | if (!(now - chart.zoom.prevMouseDown < 100)) { 50 | chart.zoom.dragStart = chart.zoom.dataPos.x 51 | chart.zoom.dragging = true 52 | chart.zoom.prevMouseDown = now 53 | } 54 | } 55 | 56 | if (args.event.type == 'mouseup') { 57 | this.move(chart, getRelativePosition(args.event, chart)) 58 | const now = Date.now() 59 | if (now - chart.zoom.prevMouseUp < 500) 60 | this.reset(chart) 61 | else if (chart.zoom.dragging) 62 | this.stopDrag(chart) 63 | chart.zoom.dragging = false 64 | chart.zoom.prevMouseUp = now 65 | } 66 | 67 | if (args.event.type == 'mouseout') { 68 | if (chart.zoom.onLeave) 69 | chart.zoom.onLeave() 70 | } 71 | 72 | }, 73 | 74 | afterDraw: function(chart, args) { 75 | 76 | if (chart.zoom.dataPos === null) 77 | return 78 | 79 | const { x: scaleX, y: scaleY } = this.scales(chart) 80 | const pixelX = scaleX.getPixelForValue(chart.zoom.dataPos.x) 81 | const minY = scaleY.getPixelForValue(scaleY.min) 82 | const maxY = scaleY.getPixelForValue(scaleY.max) 83 | 84 | if (chart.zoom.dragging) { 85 | const startX = scaleX.getPixelForValue(chart.zoom.dragStart) 86 | chart.ctx.beginPath() 87 | chart.ctx.rect(startX, minY, pixelX - startX, maxY - minY) 88 | chart.ctx.lineWidth = 1 89 | chart.ctx.strokeStyle = 'rgba(127,127,127,0.8)' 90 | chart.ctx.fillStyle = 'rgba(127,127,127,0.1)' 91 | chart.ctx.fill() 92 | chart.ctx.fillStyle = '' 93 | chart.ctx.stroke() 94 | chart.ctx.closePath() 95 | } else { 96 | chart.ctx.beginPath() 97 | chart.ctx.moveTo(pixelX, minY) 98 | chart.ctx.lineWidth = 1 99 | chart.ctx.strokeStyle = 'rgba(127,127,127,0.8)' 100 | chart.ctx.lineTo(pixelX, maxY) 101 | chart.ctx.stroke() 102 | } 103 | 104 | return true 105 | }, 106 | 107 | move: function(chart, canvasPos) { 108 | const { x: scaleX, y: scaleY } = this.scales(chart) 109 | let dataX = scaleX.getValueForPixel(canvasPos.x) 110 | let dataY = scaleY.getValueForPixel(canvasPos.y) 111 | dataX = Math.max(scaleX.min, Math.min(dataX, scaleX.max)) 112 | dataY = Math.max(scaleY.min, Math.min(dataY, scaleY.max)) 113 | chart.zoom.canvasPos = canvasPos 114 | chart.zoom.dataPos = { x: dataX, y: dataY } 115 | this.updateOnce(chart) 116 | if (chart.zoom.options.onMove) 117 | chart.zoom.options.onMove(chart.zoom.dataPos) 118 | }, 119 | 120 | zoom: function(chart, minX, maxX) { 121 | chart.zoom.dragging = false 122 | if (minX === maxX) { 123 | this.updateOnce(chart) 124 | return 125 | } 126 | chart.options.scales.x.min = minX 127 | chart.options.scales.x.max = maxX 128 | this.updateOnce(chart) 129 | this.move(chart, chart.zoom.canvasPos) 130 | if (chart.zoom.options.onZoom) 131 | chart.zoom.options.onZoom(minX, maxX) 132 | }, 133 | 134 | reset: function(chart) { 135 | chart.zoom.dragging = false 136 | delete chart.options.scales.x.min 137 | delete chart.options.scales.x.max 138 | this.updateOnce(chart) 139 | this.move(chart, chart.zoom.canvasPos) 140 | if (chart.zoom.options.onReset) 141 | chart.zoom.options.onReset() 142 | }, 143 | 144 | updateOnce: function(chart) { 145 | if (chart.zoom.updating) 146 | return 147 | chart.zoom.updating = true 148 | chart.update('none') 149 | chart.zoom.updating = false 150 | }, 151 | 152 | stopDrag: function(chart) { 153 | if (!chart.zoom.dragging) 154 | return 155 | chart.zoom.dragging = false 156 | const dragStop = chart.zoom.dataPos.x 157 | const zoomMin = Math.min(chart.zoom.dragStart, dragStop) 158 | const zoomMax = Math.max(chart.zoom.dragStart, dragStop) 159 | this.zoom(chart, zoomMin, zoomMax) 160 | }, 161 | 162 | scales: function(chart) { 163 | return { 164 | x: chart.scales.x, 165 | y: chart.scales.y 166 | } 167 | }, 168 | 169 | } 170 | 171 | const debounce = (func, wait, immediate) => { 172 | let timeout = null 173 | return function() { 174 | let context = this, args = arguments 175 | const callNow = immediate && !timeout 176 | clearTimeout(timeout) 177 | timeout = setTimeout(() => { 178 | timeout = null 179 | if (!callNow) 180 | func.apply(context, args) 181 | }, wait) 182 | if (callNow) 183 | setTimeout(() => func.apply(context, args)) 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /viewer/server.py: -------------------------------------------------------------------------------- 1 | import concurrent.futures 2 | import functools 3 | import pathlib 4 | import struct 5 | import sys 6 | 7 | import fastapi 8 | import fastapi.responses 9 | import fastapi.staticfiles 10 | 11 | sys.path.insert(0, str(pathlib.Path(__file__).parent)) 12 | 13 | import filesystems 14 | import config 15 | 16 | 17 | config = config.config 18 | app = fastapi.FastAPI( 19 | debug=config.debug, 20 | # Support extended JSON (NaN, Inf, -Inf). 21 | default_response_class=fastapi.responses.ORJSONResponse, 22 | ) 23 | basedir = config.basedir.rstrip('/') 24 | fs = dict( 25 | elements=filesystems.Elements, 26 | fileutil=filesystems.Fileutil, 27 | local=filesystems.Local, 28 | )[config.filesystem]() 29 | 30 | is_local = isinstance(fs, filesystems.Local) 31 | if config.cachedir and config.cachesize and not is_local: 32 | cachedfs = filesystems.WithFileCache(fs, config.cachedir, config.cachesize) 33 | else: 34 | cachedfs = fs 35 | 36 | 37 | @app.get('/api/exps') 38 | def get_exps(): 39 | print('GET /exps', flush=True) 40 | folders = fs.list(basedir) 41 | expids = [x.rsplit('/', 1)[-1] for x in folders] 42 | return {'exps': expids} 43 | 44 | 45 | @app.get('/api/exp/{expid}') 46 | def get_exp(expid: str): 47 | print(f'GET /exp/{expid}', flush=True) 48 | folders = find_runs(basedir + '/' + expid) 49 | folders = [x.removeprefix(str(basedir))[1:] for x in folders] 50 | runids = [x.replace('/', ':') for x in folders] 51 | return {'id': expid, 'runs': runids} 52 | 53 | 54 | @app.get('/api/run/{runid}') 55 | def get_run(runid: str): 56 | print(f'GET /run/{runid}', flush=True) 57 | folder = basedir + '/' + runid.replace(':', '/') + '/scope' 58 | children = fs.list(folder) 59 | children = [x.removeprefix(str(basedir))[1:] for x in children] 60 | colids = [x.replace('/', ':') for x in children] 61 | return {'id': runid, 'cols': colids} 62 | 63 | 64 | @app.get('/api/col/{colid}') 65 | def get_col(colid: str): 66 | print(f'GET /col/{colid}', flush=True) 67 | ext = colid.rsplit('.', 1)[-1] 68 | path = basedir + '/' + colid.replace(':', '/') 69 | runid = colid.rsplit(':', 2)[0] # Remove metric name and scope folder. 70 | if ext == 'float': 71 | buffer = fs.read(path) 72 | steps, values = tuple(zip(*struct.iter_unpack('>qd', buffer))) 73 | return {'id': colid, 'run': runid, 'steps': steps, 'values': values} 74 | elif ext in ('txt', 'png', 'jpg', 'jpeg', 'mp4', 'webm'): 75 | buffer = fs.read(path + '/index') 76 | steps, idents = tuple(zip(*struct.iter_unpack('q8s', buffer))) 77 | filenames = [f'{s:020}-{x.hex()}.{ext}' for s, x in zip(steps, idents)] 78 | values = [f'{colid}:{x}' for x in filenames] 79 | return {'id': colid, 'run': runid, 'steps': steps, 'values': values} 80 | else: 81 | raise NotImplementedError((colid, ext)) 82 | 83 | 84 | @app.get('/api/file/{fileid}') 85 | def get_file(request: fastapi.Request, fileid: str): 86 | print(f'GET /file/{fileid}', flush=True) 87 | ext = fileid.rsplit('.', 1)[-1] 88 | path = basedir + '/' + fileid.replace(':', '/') 89 | if ext in ('txt',): 90 | text = cachedfs.read(path).decode('utf-8') 91 | return {'id': fileid, 'text': text} 92 | elif ext in ('png', 'jpg', 'jpeg'): 93 | data = cachedfs.read(path) 94 | return fastapi.Response(content=data, media_type=f'image/{ext}') 95 | elif ext in ('mp4', 'webm'): 96 | filesize = cachedfs.size(path) 97 | openfn = functools.partial(cachedfs.open, path) 98 | content_type = f'video/{ext}' 99 | return RangeResponse(request, openfn, filesize, content_type) 100 | else: 101 | raise NotImplementedError((fileid, ext)) 102 | 103 | 104 | dist = pathlib.Path(__file__).parent / 'dist' 105 | app.mount('/', fastapi.staticfiles.StaticFiles(directory=dist, html=True)) 106 | 107 | 108 | def find_runs(folder, maxdepth=config.maxdepth, workers=64): 109 | if not workers: 110 | runs = [] 111 | queue = [(folder, 0)] 112 | while queue: 113 | node, depth = queue.pop(0) 114 | children = fs.list(node) 115 | if any(x.endswith('/scope') for x in children): 116 | runs.append(node) 117 | elif depth < maxdepth: 118 | queue += [(x, depth + 1) for x in children] 119 | return runs 120 | runs = [] 121 | with concurrent.futures.ThreadPoolExecutor(workers) as pool: 122 | future = pool.submit(fs.list, folder) 123 | future.parent = folder 124 | future.depth = 0 125 | queue = [future] 126 | while queue: 127 | current = queue.pop(0) 128 | children = current.result() 129 | if any(x.endswith('/scope') for x in children): 130 | runs.append(current.parent) 131 | elif current.depth < maxdepth: 132 | for child in children: 133 | future = pool.submit(fs.list, child) 134 | future.parent = child 135 | future.depth = current.depth + 1 136 | queue.append(future) 137 | return runs 138 | 139 | 140 | def RangeResponse(request, openfn, filesize, content_type): 141 | headers = { 142 | 'content-type': content_type, 143 | 'accept-ranges': 'bytes', 144 | 'content-length': str(filesize), 145 | 'access-control-expose-headers': ( 146 | 'content-type, accept-ranges, content-length, ' 147 | 'content-range, content-encoding' 148 | ), 149 | } 150 | range_header = request.headers.get('range') 151 | if range_header: 152 | try: 153 | h = range_header.replace('bytes=', '').split('-') 154 | start = int(h[0]) if h[0] != '' else 0 155 | end = int(h[1]) if h[1] != '' else filesize - 1 156 | except ValueError: 157 | raise fastapi.HTTPException( 158 | fastapi.status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE, 159 | detail=f'Invalid request range (Range:{range_header!r})') 160 | if start > end or start < 0 or end > filesize - 1: 161 | raise fastapi.HTTPException( 162 | fastapi.status.HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE, 163 | detail=f'Invalid request range (Range:{range_header!r})') 164 | size = end - start + 1 165 | headers['content-length'] = str(size) 166 | headers['content-range'] = f'bytes {start}-{end}/{filesize}' 167 | status_code = fastapi.status.HTTP_206_PARTIAL_CONTENT 168 | else: 169 | start = 0 170 | end = filesize - 1 171 | status_code = fastapi.status.HTTP_200_OK 172 | stop = end + 1 173 | def iterfile(chunksize=int(2e5)): 174 | with openfn(start, stop) as f: 175 | total = stop - start 176 | nbytes = 0 177 | while nbytes < total: 178 | chunk = f.read(min(chunksize, total - nbytes)) 179 | nbytes += len(chunk) 180 | yield chunk 181 | return fastapi.responses.StreamingResponse( 182 | iterfile(), headers=headers, status_code=status_code) 183 | -------------------------------------------------------------------------------- /scope/formats.py: -------------------------------------------------------------------------------- 1 | import io 2 | import struct 3 | import time 4 | 5 | import numpy as np 6 | import PIL.Image 7 | 8 | 9 | class Float: 10 | 11 | @property 12 | def extension(self): 13 | return 'float' 14 | 15 | def valid(self, x): 16 | if isinstance(x, (int, float)): 17 | return True 18 | if isinstance(x, np.ndarray): 19 | return x.ndim == 0 and np.isreal(x) 20 | return False 21 | 22 | def convert(self, x): 23 | return np.asarray(x, np.float64) 24 | 25 | def create(self, path): 26 | pass 27 | 28 | def write(self, path, steps, values): 29 | table_append(path, '>qd', steps, values) 30 | 31 | def read(self, path): 32 | steps, values = table_read(path, '>qd') 33 | steps = np.int64(steps) 34 | values = np.float64(values) 35 | return steps, values 36 | 37 | def length(self, path): 38 | return table_length(path, '>qd') 39 | 40 | 41 | class Text: 42 | 43 | @property 44 | def extension(self): 45 | return 'txt' 46 | 47 | def valid(self, x): 48 | return isinstance(x, str) 49 | 50 | def convert(self, x): 51 | return x 52 | 53 | def create(self, path): 54 | path.mkdir(exist_ok=True) 55 | 56 | def write(self, path, steps, values): 57 | files_write(path, steps, values, self.encode) 58 | 59 | def read(self, path): 60 | return files_read(path) 61 | 62 | def encode(self, value): 63 | return value.encode('utf-8') 64 | 65 | def decode(self, buffer): 66 | return buffer.deceode('utf-8') 67 | 68 | def length(self, path): 69 | return files_length(path) 70 | 71 | 72 | class Image: 73 | 74 | def __init__(self, ext='png', quality=90): 75 | self.ext = ext 76 | self.quality = quality 77 | 78 | @property 79 | def extension(self): 80 | return self.ext 81 | 82 | def valid(self, x): 83 | return ( 84 | isinstance(x, np.ndarray) and 85 | x.dtype == np.uint8 and 86 | x.ndim == 3 and 87 | x.shape[-1] in (1, 3)) 88 | 89 | def convert(self, x): 90 | return x 91 | 92 | def create(self, path): 93 | path.mkdir(exist_ok=True) 94 | 95 | def write(self, path, steps, values): 96 | files_write(path, steps, values, self.encode) 97 | 98 | def read(self, path): 99 | return files_read(path) 100 | 101 | def encode(self, value): 102 | if value.shape[-1] == 1: 103 | value = value.repeat(3, -1) 104 | fmt = ('jpeg' if self.ext == 'jpg' else self.ext).upper() 105 | fp = io.BytesIO() 106 | PIL.Image.fromarray(value).save(fp, fmt, quality=self.quality) 107 | return fp.getvalue() 108 | 109 | def decode(self, buffer): 110 | return np.asarray(PIL.Image.open(io.BytesIO(buffer)).convert('RGB')) 111 | 112 | def length(self, path): 113 | return files_length(path) 114 | 115 | 116 | class Video: 117 | 118 | def __init__(self, ext='mp4', codec='h264', fps=10): 119 | self.ext = ext 120 | self.codec = codec 121 | self.fps = fps 122 | 123 | @property 124 | def extension(self): 125 | return self.ext 126 | 127 | def valid(self, x): 128 | return ( 129 | isinstance(x, np.ndarray) and 130 | x.dtype == np.uint8 and 131 | x.ndim == 4 and 132 | x.shape[-1] in (1, 3)) 133 | 134 | def convert(self, x): 135 | return x 136 | 137 | def create(self, path): 138 | path.mkdir(exist_ok=True) 139 | 140 | def write(self, path, steps, values): 141 | files_write(path, steps, values, self.encode) 142 | 143 | def read(self, path): 144 | return files_read(path) 145 | 146 | def encode(self, value): 147 | import av 148 | if value.shape[-1] == 1: 149 | value = value.repeat(3, -1) 150 | T, H, W, _ = value.shape 151 | fp = io.BytesIO() 152 | output = av.open(fp, mode='w', format=self.ext) 153 | stream = output.add_stream(self.codec, rate=self.fps) 154 | stream.width = W 155 | stream.height = H 156 | stream.pix_fmt = 'yuv420p' 157 | for t in range(T): 158 | frame = av.VideoFrame.from_ndarray(value[t], format='rgb24') 159 | frame.pts = t 160 | output.mux(stream.encode(frame)) 161 | output.mux(stream.encode(None)) 162 | output.close() 163 | return fp.getvalue() 164 | 165 | def decode(self, buffer): 166 | import av 167 | container = av.open(io.BytesIO(buffer)) 168 | value = [] 169 | for frame in container.decode(video=0): 170 | value.append(frame.to_ndarray(format='rgb24')) 171 | value = np.stack(value) 172 | container.close() 173 | return value 174 | 175 | def length(self, path): 176 | return files_length(path) 177 | 178 | 179 | class MediapyVideo: 180 | 181 | def __init__(self, ext='mp4', fps=10): 182 | self.ext = ext 183 | self.fps = fps 184 | 185 | @property 186 | def extension(self): 187 | return self.ext 188 | 189 | def valid(self, x): 190 | return ( 191 | isinstance(x, np.ndarray) and 192 | x.dtype == np.uint8 and 193 | x.ndim == 4 and 194 | x.shape[-1] in (1, 3)) 195 | 196 | def convert(self, x): 197 | if x.shape[-1] == 1: 198 | x = x.repeat(3, -1) 199 | return x 200 | 201 | def create(self, path): 202 | path.mkdir(exist_ok=True) 203 | 204 | def write(self, path, steps, values): 205 | files_write(path, steps, values, self.encode) 206 | 207 | def read(self, path): 208 | return files_read(path) 209 | 210 | def encode(self, value): 211 | import mediapy 212 | return mediapy.compress_video(value, fps=self.fps) 213 | 214 | def decode(self, buffer): 215 | import mediapy 216 | return mediapy.decompress_video(buffer) 217 | 218 | def length(self, path): 219 | return files_length(path) 220 | 221 | 222 | def table_append(filename, fmt, *cols): 223 | rows = tuple(zip(*cols)) 224 | size = struct.calcsize(fmt) 225 | buffer = bytearray(len(rows) * size) 226 | for index, row in enumerate(rows): 227 | struct.pack_into(fmt, buffer, index * size, *row) 228 | with filename.open('ab') as f: 229 | f.write(buffer) 230 | 231 | 232 | def table_read(filename, fmt, start=0, stop=None): 233 | assert stop is None or start < stop, (start, stop) 234 | if start == 0 and stop is None: 235 | buffer = filename.read_bytes() 236 | else: 237 | size = struct.calcsize(fmt) 238 | with filename.open('rb') as f: 239 | start and f.seek(start * size) 240 | buffer = f.read((stop - start) * size if stop else None) 241 | rows = struct.iter_unpack(fmt, buffer) 242 | cols = tuple(zip(*rows)) 243 | return cols 244 | 245 | 246 | def table_length(filename, fmt): 247 | return filename.stat().st_size // struct.calcsize(fmt) 248 | 249 | 250 | def files_write(path, steps, values, encode): 251 | rng = np.random.default_rng(seed=None) 252 | prefix = int(time.time()).to_bytes(4, 'big') 253 | idents = [prefix + rng.bytes(4) for _ in range(len(steps))] 254 | for ident, step, value in zip(idents, steps, values): 255 | filename = f'{step:020}-{ident.hex()}{path.suffix}' 256 | buffer = encode(value) 257 | with (path / filename).open('wb') as f: 258 | f.write(buffer) 259 | table_append(path / 'index', 'q8s', steps, idents) 260 | 261 | 262 | def files_read(path): 263 | steps, idents = table_read(path / 'index', 'q8s') 264 | filenames = [ 265 | path / f'{step:020}-{ident.hex()}{path.suffix}' 266 | for step, ident in zip(steps, idents)] 267 | steps = np.int64(steps) 268 | return steps, filenames 269 | 270 | 271 | def files_length(path): 272 | return table_length(path / 'index', 'q8s') 273 | -------------------------------------------------------------------------------- /viewer/src/store.js: -------------------------------------------------------------------------------- 1 | import { reactive, computed, watch, ref, shallowRef } from 'vue' 2 | import { saveStorage, loadStorage } from './storage.js' 3 | 4 | /***************************************************************************** 5 | * Helpers 6 | *****************************************************************************/ 7 | 8 | async function get(url, delay = 0) { 9 | if (url.indexOf('[') >= 0) 10 | throw new Error(`Invalid URL: ${url}`) 11 | console.log(url) 12 | const result = await (await fetch(url)).json() 13 | // await new Promise(x => setTimeout(x, 500)) // TODO 14 | if (delay > 0) 15 | await new Promise(x => setTimeout(x, delay)) // TODO 16 | return result 17 | } 18 | 19 | function colToMet(col) { 20 | return col.substr(col.lastIndexOf(':') + 1) 21 | } 22 | 23 | function colToRun(col) { 24 | // Remove metric name and scope folder. 25 | return col.split(':').slice(0, -2).join(':') 26 | } 27 | 28 | /***************************************************************************** 29 | * State 30 | *****************************************************************************/ 31 | 32 | const pendingEids = ref(false) 33 | const pendingExps = ref(new Set()) 34 | const pendingRuns = ref(new Set()) 35 | const pendingCols = ref(new Set()) 36 | 37 | const cachedEids = ref(loadStorage('cachedEids', [])) 38 | const cachedExps = ref(loadStorage('cachedExps', {})) 39 | const cachedRuns = ref(loadStorage('cachedRuns', {})) 40 | const cachedCols = ref({}) 41 | 42 | const selExps = ref(loadStorage('selExps', new Set())) 43 | const selRuns = ref(loadStorage('selRuns', new Set())) 44 | const selMets = ref(loadStorage('selMets', new Set())) 45 | 46 | /***************************************************************************** 47 | * Persistence 48 | *****************************************************************************/ 49 | 50 | watch(() => cachedEids, x => saveStorage('cachedEids', x.value), { deep: 2 }) 51 | watch(() => cachedExps, x => saveStorage('cachedExps', x.value), { deep: 2 }) 52 | watch(() => cachedRuns, x => saveStorage('cachedRuns', x.value), { deep: 2 }) 53 | watch(() => selExps, x => saveStorage('selExps', x.value), { deep: 2 }) 54 | watch(() => selRuns, x => saveStorage('selRuns', x.value), { deep: 2 }) 55 | watch(() => selMets, x => saveStorage('selMets', x.value), { deep: 2 }) 56 | 57 | /***************************************************************************** 58 | * Computed 59 | *****************************************************************************/ 60 | 61 | const availableExps = computed(() => { 62 | return cachedEids.value.sort() 63 | }) 64 | 65 | const availableRuns = computed(() => { 66 | return [...selExps.value] 67 | .filter(expid => expid in cachedExps.value) 68 | .flatMap(expid => cachedExps.value[expid]['runs']) 69 | .sort() 70 | }) 71 | 72 | const availableMets = computed(() => { 73 | const results = [...selRuns.value] 74 | .filter(runid => runid in cachedRuns.value) 75 | .flatMap(runid => cachedRuns.value[runid]['cols']) 76 | .map(colid => colToMet(colid)) 77 | return [...new Set(results)].sort() 78 | }) 79 | 80 | const availableCards = computed(() => { 81 | return [...selMets.value] 82 | .sort() 83 | .map(met => { 84 | const ext = met.substr(met.lastIndexOf('.') + 1) 85 | const cols = [...selRuns.value] 86 | .filter(runid => runid in cachedRuns.value) 87 | .flatMap(runid => cachedRuns.value[runid]['cols']) 88 | .filter(colid => met === colToMet(colid)) 89 | .sort() 90 | return { name: met, ext: ext, cols: cols } 91 | }) 92 | }) 93 | 94 | const availableCols = computed(() => { 95 | return cachedCols.value 96 | }) 97 | 98 | /***************************************************************************** 99 | * Fetching 100 | *****************************************************************************/ 101 | 102 | async function updateEids(force = false) { 103 | if (cachedEids.value.length > 0 && !force) 104 | return 105 | pendingEids.value = true 106 | get('/api/exps') 107 | .then(data => cachedEids.value = data['exps']) 108 | .finally(() => pendingEids.value = false) 109 | } 110 | 111 | async function updateExps(force = false) { 112 | [...selExps.value] 113 | .filter(expid => !(expid in pendingExps.value)) 114 | .filter(expid => !(expid in cachedExps.value) || force) 115 | .map(expid => { pendingExps.value.add(expid); return expid }) 116 | .map(expid => get(`/api/exp/${expid}`) 117 | .then(data => cachedExps.value[data.id] = shallowRef(data)) 118 | .finally(() => pendingExps.value.delete(expid))) 119 | } 120 | 121 | async function updateRuns(force = false) { 122 | [...selRuns.value] 123 | .filter(runid => !(runid in pendingRuns.value)) 124 | .filter(runid => !(runid in cachedRuns.value) || force) 125 | .map(runid => { pendingRuns.value.add(runid); return runid }) 126 | .map(runid => get(`/api/run/${runid}`) 127 | .then(data => cachedRuns.value[data.id] = shallowRef(data)) 128 | .finally(() => pendingRuns.value.delete(runid))) 129 | } 130 | 131 | async function updateCols(force = false, clearOld = true) { 132 | if (clearOld) { 133 | Object.keys(cachedCols.value) 134 | .filter(colid => ( 135 | !selMets.value.has(colToMet(colid)) || 136 | !selRuns.value.has(colToRun(colid)))) 137 | .map(colid => delete cachedCols.value[colid]) 138 | } 139 | availableCards.value 140 | .flatMap(card => card['cols']) 141 | .filter(colid => !pendingCols.value.has(colid)) 142 | .filter(colid => !(colid in cachedCols.value) || force) 143 | .map(colid => { pendingCols.value.add(colid); return colid }) 144 | .map(colid => get(`/api/col/${colid}`) 145 | .then(data => cachedCols.value[data.id] = shallowRef(data)) 146 | .finally(() => pendingCols.value.delete(colid))) 147 | } 148 | 149 | async function refresh() { 150 | Object.keys(cachedExps.value) 151 | .filter(expid => !selExps.value.has(expid)) 152 | .map(expid => delete cachedExps.value[expid]) 153 | Object.keys(cachedRuns.value) 154 | .filter(runid => !selRuns.value.has(runid)) 155 | .map(runid => delete cachedRuns.value[runid]) 156 | updateCols(true) 157 | updateExps(true) 158 | updateRuns(true) 159 | updateEids(true) 160 | } 161 | 162 | /***************************************************************************** 163 | * Automatic fetching 164 | *****************************************************************************/ 165 | 166 | updateEids() 167 | setTimeout(() => { 168 | watch(() => selExps, () => updateExps(false), { deep: 2 }) 169 | watch(() => selRuns, () => updateRuns(false), { deep: 2 }) 170 | watch(() => selRuns, () => updateCols(false), { deep: 2 }) 171 | watch(() => selMets, () => updateCols(false), { deep: 2 }) 172 | 173 | // TODO: Do this better. This might also not refresh cards when the runs and 174 | // metrics stay the same but their values have progressed. 175 | watch(() => availableCards, () => updateCols(false), { deep: 3 }) 176 | 177 | updateCols(false) 178 | }, 200) 179 | 180 | /***************************************************************************** 181 | * Other 182 | *****************************************************************************/ 183 | 184 | const options = reactive(loadStorage('options', { 185 | binsize: null, 186 | stepsel: null, 187 | })) 188 | 189 | watch(() => options, x => saveStorage('options', x), { deep: true }) 190 | 191 | /***************************************************************************** 192 | * Export 193 | *****************************************************************************/ 194 | 195 | const store = { 196 | 197 | pendingEids, 198 | pendingExps, 199 | pendingRuns, 200 | pendingCols, 201 | 202 | get, 203 | refresh, 204 | 205 | // cachedEids, 206 | // cachedExps, 207 | // cachedRuns, 208 | // cachedCols, 209 | 210 | selExps, 211 | selRuns, 212 | selMets, 213 | 214 | // updateEids, 215 | // updateExps, 216 | // updateRuns, 217 | 218 | availableExps, 219 | availableRuns, 220 | availableMets, 221 | availableCards, 222 | availableCols, 223 | 224 | options, 225 | 226 | } 227 | 228 | export default store 229 | -------------------------------------------------------------------------------- /viewer/src/CardFloat.vue: -------------------------------------------------------------------------------- 1 | 276 | 277 | 303 | 304 | 321 | -------------------------------------------------------------------------------- /viewer/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scope", 3 | "version": "0.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "scope", 9 | "version": "0.0.0", 10 | "dependencies": { 11 | "chart.js": "^4.4.4", 12 | "vue": "^3.5.8" 13 | }, 14 | "devDependencies": { 15 | "@vitejs/plugin-vue": "^5.0.5", 16 | "concurrently": "^9.0.1", 17 | "vite": "^5.3.1", 18 | "vite-plugin-vue-devtools": "^7.3.1" 19 | } 20 | }, 21 | "node_modules/@ampproject/remapping": { 22 | "version": "2.3.0", 23 | "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", 24 | "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", 25 | "dev": true, 26 | "dependencies": { 27 | "@jridgewell/gen-mapping": "^0.3.5", 28 | "@jridgewell/trace-mapping": "^0.3.24" 29 | }, 30 | "engines": { 31 | "node": ">=6.0.0" 32 | } 33 | }, 34 | "node_modules/@antfu/utils": { 35 | "version": "0.7.10", 36 | "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", 37 | "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", 38 | "dev": true, 39 | "funding": { 40 | "url": "https://github.com/sponsors/antfu" 41 | } 42 | }, 43 | "node_modules/@babel/code-frame": { 44 | "version": "7.25.9", 45 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.9.tgz", 46 | "integrity": "sha512-z88xeGxnzehn2sqZ8UdGQEvYErF1odv2CftxInpSYJt6uHuPe9YjahKZITGs3l5LeI9d2ROG+obuDAoSlqbNfQ==", 47 | "dev": true, 48 | "dependencies": { 49 | "@babel/highlight": "^7.25.9", 50 | "picocolors": "^1.0.0" 51 | }, 52 | "engines": { 53 | "node": ">=6.9.0" 54 | } 55 | }, 56 | "node_modules/@babel/compat-data": { 57 | "version": "7.25.9", 58 | "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.9.tgz", 59 | "integrity": "sha512-yD+hEuJ/+wAJ4Ox2/rpNv5HIuPG82x3ZlQvYVn8iYCprdxzE7P1udpGF1jyjQVBU4dgznN+k2h103vxZ7NdPyw==", 60 | "dev": true, 61 | "engines": { 62 | "node": ">=6.9.0" 63 | } 64 | }, 65 | "node_modules/@babel/core": { 66 | "version": "7.25.9", 67 | "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.9.tgz", 68 | "integrity": "sha512-WYvQviPw+Qyib0v92AwNIrdLISTp7RfDkM7bPqBvpbnhY4wq8HvHBZREVdYDXk98C8BkOIVnHAY3yvj7AVISxQ==", 69 | "dev": true, 70 | "dependencies": { 71 | "@ampproject/remapping": "^2.2.0", 72 | "@babel/code-frame": "^7.25.9", 73 | "@babel/generator": "^7.25.9", 74 | "@babel/helper-compilation-targets": "^7.25.9", 75 | "@babel/helper-module-transforms": "^7.25.9", 76 | "@babel/helpers": "^7.25.9", 77 | "@babel/parser": "^7.25.9", 78 | "@babel/template": "^7.25.9", 79 | "@babel/traverse": "^7.25.9", 80 | "@babel/types": "^7.25.9", 81 | "convert-source-map": "^2.0.0", 82 | "debug": "^4.1.0", 83 | "gensync": "^1.0.0-beta.2", 84 | "json5": "^2.2.3", 85 | "semver": "^6.3.1" 86 | }, 87 | "engines": { 88 | "node": ">=6.9.0" 89 | }, 90 | "funding": { 91 | "type": "opencollective", 92 | "url": "https://opencollective.com/babel" 93 | } 94 | }, 95 | "node_modules/@babel/generator": { 96 | "version": "7.25.9", 97 | "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.9.tgz", 98 | "integrity": "sha512-omlUGkr5EaoIJrhLf9CJ0TvjBRpd9+AXRG//0GEQ9THSo8wPiTlbpy1/Ow8ZTrbXpjd9FHXfbFQx32I04ht0FA==", 99 | "dev": true, 100 | "dependencies": { 101 | "@babel/types": "^7.25.9", 102 | "@jridgewell/gen-mapping": "^0.3.5", 103 | "@jridgewell/trace-mapping": "^0.3.25", 104 | "jsesc": "^3.0.2" 105 | }, 106 | "engines": { 107 | "node": ">=6.9.0" 108 | } 109 | }, 110 | "node_modules/@babel/helper-annotate-as-pure": { 111 | "version": "7.25.9", 112 | "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz", 113 | "integrity": "sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==", 114 | "dev": true, 115 | "dependencies": { 116 | "@babel/types": "^7.25.9" 117 | }, 118 | "engines": { 119 | "node": ">=6.9.0" 120 | } 121 | }, 122 | "node_modules/@babel/helper-compilation-targets": { 123 | "version": "7.25.9", 124 | "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", 125 | "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", 126 | "dev": true, 127 | "dependencies": { 128 | "@babel/compat-data": "^7.25.9", 129 | "@babel/helper-validator-option": "^7.25.9", 130 | "browserslist": "^4.24.0", 131 | "lru-cache": "^5.1.1", 132 | "semver": "^6.3.1" 133 | }, 134 | "engines": { 135 | "node": ">=6.9.0" 136 | } 137 | }, 138 | "node_modules/@babel/helper-create-class-features-plugin": { 139 | "version": "7.25.9", 140 | "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz", 141 | "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==", 142 | "dev": true, 143 | "dependencies": { 144 | "@babel/helper-annotate-as-pure": "^7.25.9", 145 | "@babel/helper-member-expression-to-functions": "^7.25.9", 146 | "@babel/helper-optimise-call-expression": "^7.25.9", 147 | "@babel/helper-replace-supers": "^7.25.9", 148 | "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", 149 | "@babel/traverse": "^7.25.9", 150 | "semver": "^6.3.1" 151 | }, 152 | "engines": { 153 | "node": ">=6.9.0" 154 | }, 155 | "peerDependencies": { 156 | "@babel/core": "^7.0.0" 157 | } 158 | }, 159 | "node_modules/@babel/helper-member-expression-to-functions": { 160 | "version": "7.25.9", 161 | "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.9.tgz", 162 | "integrity": "sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==", 163 | "dev": true, 164 | "dependencies": { 165 | "@babel/traverse": "^7.25.9", 166 | "@babel/types": "^7.25.9" 167 | }, 168 | "engines": { 169 | "node": ">=6.9.0" 170 | } 171 | }, 172 | "node_modules/@babel/helper-module-imports": { 173 | "version": "7.25.9", 174 | "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", 175 | "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", 176 | "dev": true, 177 | "dependencies": { 178 | "@babel/traverse": "^7.25.9", 179 | "@babel/types": "^7.25.9" 180 | }, 181 | "engines": { 182 | "node": ">=6.9.0" 183 | } 184 | }, 185 | "node_modules/@babel/helper-module-transforms": { 186 | "version": "7.25.9", 187 | "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.9.tgz", 188 | "integrity": "sha512-TvLZY/F3+GvdRYFZFyxMvnsKi+4oJdgZzU3BoGN9Uc2d9C6zfNwJcKKhjqLAhK8i46mv93jsO74fDh3ih6rpHA==", 189 | "dev": true, 190 | "dependencies": { 191 | "@babel/helper-module-imports": "^7.25.9", 192 | "@babel/helper-simple-access": "^7.25.9", 193 | "@babel/helper-validator-identifier": "^7.25.9", 194 | "@babel/traverse": "^7.25.9" 195 | }, 196 | "engines": { 197 | "node": ">=6.9.0" 198 | }, 199 | "peerDependencies": { 200 | "@babel/core": "^7.0.0" 201 | } 202 | }, 203 | "node_modules/@babel/helper-optimise-call-expression": { 204 | "version": "7.25.9", 205 | "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.25.9.tgz", 206 | "integrity": "sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==", 207 | "dev": true, 208 | "dependencies": { 209 | "@babel/types": "^7.25.9" 210 | }, 211 | "engines": { 212 | "node": ">=6.9.0" 213 | } 214 | }, 215 | "node_modules/@babel/helper-plugin-utils": { 216 | "version": "7.25.9", 217 | "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz", 218 | "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==", 219 | "dev": true, 220 | "engines": { 221 | "node": ">=6.9.0" 222 | } 223 | }, 224 | "node_modules/@babel/helper-replace-supers": { 225 | "version": "7.25.9", 226 | "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz", 227 | "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==", 228 | "dev": true, 229 | "dependencies": { 230 | "@babel/helper-member-expression-to-functions": "^7.25.9", 231 | "@babel/helper-optimise-call-expression": "^7.25.9", 232 | "@babel/traverse": "^7.25.9" 233 | }, 234 | "engines": { 235 | "node": ">=6.9.0" 236 | }, 237 | "peerDependencies": { 238 | "@babel/core": "^7.0.0" 239 | } 240 | }, 241 | "node_modules/@babel/helper-simple-access": { 242 | "version": "7.25.9", 243 | "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.25.9.tgz", 244 | "integrity": "sha512-c6WHXuiaRsJTyHYLJV75t9IqsmTbItYfdj99PnzYGQZkYKvan5/2jKJ7gu31J3/BJ/A18grImSPModuyG/Eo0Q==", 245 | "dev": true, 246 | "dependencies": { 247 | "@babel/traverse": "^7.25.9", 248 | "@babel/types": "^7.25.9" 249 | }, 250 | "engines": { 251 | "node": ">=6.9.0" 252 | } 253 | }, 254 | "node_modules/@babel/helper-skip-transparent-expression-wrappers": { 255 | "version": "7.25.9", 256 | "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.25.9.tgz", 257 | "integrity": "sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==", 258 | "dev": true, 259 | "dependencies": { 260 | "@babel/traverse": "^7.25.9", 261 | "@babel/types": "^7.25.9" 262 | }, 263 | "engines": { 264 | "node": ">=6.9.0" 265 | } 266 | }, 267 | "node_modules/@babel/helper-string-parser": { 268 | "version": "7.25.9", 269 | "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", 270 | "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", 271 | "engines": { 272 | "node": ">=6.9.0" 273 | } 274 | }, 275 | "node_modules/@babel/helper-validator-identifier": { 276 | "version": "7.25.9", 277 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", 278 | "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", 279 | "engines": { 280 | "node": ">=6.9.0" 281 | } 282 | }, 283 | "node_modules/@babel/helper-validator-option": { 284 | "version": "7.25.9", 285 | "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", 286 | "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", 287 | "dev": true, 288 | "engines": { 289 | "node": ">=6.9.0" 290 | } 291 | }, 292 | "node_modules/@babel/helpers": { 293 | "version": "7.25.9", 294 | "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.9.tgz", 295 | "integrity": "sha512-oKWp3+usOJSzDZOucZUAMayhPz/xVjzymyDzUN8dk0Wd3RWMlGLXi07UCQ/CgQVb8LvXx3XBajJH4XGgkt7H7g==", 296 | "dev": true, 297 | "dependencies": { 298 | "@babel/template": "^7.25.9", 299 | "@babel/types": "^7.25.9" 300 | }, 301 | "engines": { 302 | "node": ">=6.9.0" 303 | } 304 | }, 305 | "node_modules/@babel/highlight": { 306 | "version": "7.25.9", 307 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz", 308 | "integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==", 309 | "dev": true, 310 | "dependencies": { 311 | "@babel/helper-validator-identifier": "^7.25.9", 312 | "chalk": "^2.4.2", 313 | "js-tokens": "^4.0.0", 314 | "picocolors": "^1.0.0" 315 | }, 316 | "engines": { 317 | "node": ">=6.9.0" 318 | } 319 | }, 320 | "node_modules/@babel/highlight/node_modules/ansi-styles": { 321 | "version": "3.2.1", 322 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 323 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 324 | "dev": true, 325 | "dependencies": { 326 | "color-convert": "^1.9.0" 327 | }, 328 | "engines": { 329 | "node": ">=4" 330 | } 331 | }, 332 | "node_modules/@babel/highlight/node_modules/chalk": { 333 | "version": "2.4.2", 334 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 335 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 336 | "dev": true, 337 | "dependencies": { 338 | "ansi-styles": "^3.2.1", 339 | "escape-string-regexp": "^1.0.5", 340 | "supports-color": "^5.3.0" 341 | }, 342 | "engines": { 343 | "node": ">=4" 344 | } 345 | }, 346 | "node_modules/@babel/highlight/node_modules/color-convert": { 347 | "version": "1.9.3", 348 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 349 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 350 | "dev": true, 351 | "dependencies": { 352 | "color-name": "1.1.3" 353 | } 354 | }, 355 | "node_modules/@babel/highlight/node_modules/color-name": { 356 | "version": "1.1.3", 357 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 358 | "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", 359 | "dev": true 360 | }, 361 | "node_modules/@babel/highlight/node_modules/has-flag": { 362 | "version": "3.0.0", 363 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 364 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 365 | "dev": true, 366 | "engines": { 367 | "node": ">=4" 368 | } 369 | }, 370 | "node_modules/@babel/highlight/node_modules/supports-color": { 371 | "version": "5.5.0", 372 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 373 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 374 | "dev": true, 375 | "dependencies": { 376 | "has-flag": "^3.0.0" 377 | }, 378 | "engines": { 379 | "node": ">=4" 380 | } 381 | }, 382 | "node_modules/@babel/parser": { 383 | "version": "7.25.9", 384 | "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.9.tgz", 385 | "integrity": "sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==", 386 | "dependencies": { 387 | "@babel/types": "^7.25.9" 388 | }, 389 | "bin": { 390 | "parser": "bin/babel-parser.js" 391 | }, 392 | "engines": { 393 | "node": ">=6.0.0" 394 | } 395 | }, 396 | "node_modules/@babel/plugin-proposal-decorators": { 397 | "version": "7.25.9", 398 | "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.25.9.tgz", 399 | "integrity": "sha512-smkNLL/O1ezy9Nhy4CNosc4Va+1wo5w4gzSZeLe6y6dM4mmHfYOCPolXQPHQxonZCF+ZyebxN9vqOolkYrSn5g==", 400 | "dev": true, 401 | "dependencies": { 402 | "@babel/helper-create-class-features-plugin": "^7.25.9", 403 | "@babel/helper-plugin-utils": "^7.25.9", 404 | "@babel/plugin-syntax-decorators": "^7.25.9" 405 | }, 406 | "engines": { 407 | "node": ">=6.9.0" 408 | }, 409 | "peerDependencies": { 410 | "@babel/core": "^7.0.0-0" 411 | } 412 | }, 413 | "node_modules/@babel/plugin-syntax-decorators": { 414 | "version": "7.25.9", 415 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.25.9.tgz", 416 | "integrity": "sha512-ryzI0McXUPJnRCvMo4lumIKZUzhYUO/ScI+Mz4YVaTLt04DHNSjEUjKVvbzQjZFLuod/cYEc07mJWhzl6v4DPg==", 417 | "dev": true, 418 | "dependencies": { 419 | "@babel/helper-plugin-utils": "^7.25.9" 420 | }, 421 | "engines": { 422 | "node": ">=6.9.0" 423 | }, 424 | "peerDependencies": { 425 | "@babel/core": "^7.0.0-0" 426 | } 427 | }, 428 | "node_modules/@babel/plugin-syntax-import-attributes": { 429 | "version": "7.25.9", 430 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.9.tgz", 431 | "integrity": "sha512-u3EN9ub8LyYvgTnrgp8gboElouayiwPdnM7x5tcnW3iSt09/lQYPwMNK40I9IUxo7QOZhAsPHCmmuO7EPdruqg==", 432 | "dev": true, 433 | "dependencies": { 434 | "@babel/helper-plugin-utils": "^7.25.9" 435 | }, 436 | "engines": { 437 | "node": ">=6.9.0" 438 | }, 439 | "peerDependencies": { 440 | "@babel/core": "^7.0.0-0" 441 | } 442 | }, 443 | "node_modules/@babel/plugin-syntax-import-meta": { 444 | "version": "7.10.4", 445 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", 446 | "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", 447 | "dev": true, 448 | "dependencies": { 449 | "@babel/helper-plugin-utils": "^7.10.4" 450 | }, 451 | "peerDependencies": { 452 | "@babel/core": "^7.0.0-0" 453 | } 454 | }, 455 | "node_modules/@babel/plugin-syntax-jsx": { 456 | "version": "7.25.9", 457 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz", 458 | "integrity": "sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==", 459 | "dev": true, 460 | "dependencies": { 461 | "@babel/helper-plugin-utils": "^7.25.9" 462 | }, 463 | "engines": { 464 | "node": ">=6.9.0" 465 | }, 466 | "peerDependencies": { 467 | "@babel/core": "^7.0.0-0" 468 | } 469 | }, 470 | "node_modules/@babel/plugin-syntax-typescript": { 471 | "version": "7.25.9", 472 | "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz", 473 | "integrity": "sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==", 474 | "dev": true, 475 | "dependencies": { 476 | "@babel/helper-plugin-utils": "^7.25.9" 477 | }, 478 | "engines": { 479 | "node": ">=6.9.0" 480 | }, 481 | "peerDependencies": { 482 | "@babel/core": "^7.0.0-0" 483 | } 484 | }, 485 | "node_modules/@babel/plugin-transform-typescript": { 486 | "version": "7.25.9", 487 | "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz", 488 | "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==", 489 | "dev": true, 490 | "dependencies": { 491 | "@babel/helper-annotate-as-pure": "^7.25.9", 492 | "@babel/helper-create-class-features-plugin": "^7.25.9", 493 | "@babel/helper-plugin-utils": "^7.25.9", 494 | "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9", 495 | "@babel/plugin-syntax-typescript": "^7.25.9" 496 | }, 497 | "engines": { 498 | "node": ">=6.9.0" 499 | }, 500 | "peerDependencies": { 501 | "@babel/core": "^7.0.0-0" 502 | } 503 | }, 504 | "node_modules/@babel/template": { 505 | "version": "7.25.9", 506 | "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", 507 | "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", 508 | "dev": true, 509 | "dependencies": { 510 | "@babel/code-frame": "^7.25.9", 511 | "@babel/parser": "^7.25.9", 512 | "@babel/types": "^7.25.9" 513 | }, 514 | "engines": { 515 | "node": ">=6.9.0" 516 | } 517 | }, 518 | "node_modules/@babel/traverse": { 519 | "version": "7.25.9", 520 | "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", 521 | "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", 522 | "dev": true, 523 | "dependencies": { 524 | "@babel/code-frame": "^7.25.9", 525 | "@babel/generator": "^7.25.9", 526 | "@babel/parser": "^7.25.9", 527 | "@babel/template": "^7.25.9", 528 | "@babel/types": "^7.25.9", 529 | "debug": "^4.3.1", 530 | "globals": "^11.1.0" 531 | }, 532 | "engines": { 533 | "node": ">=6.9.0" 534 | } 535 | }, 536 | "node_modules/@babel/types": { 537 | "version": "7.25.9", 538 | "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.9.tgz", 539 | "integrity": "sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==", 540 | "dependencies": { 541 | "@babel/helper-string-parser": "^7.25.9", 542 | "@babel/helper-validator-identifier": "^7.25.9" 543 | }, 544 | "engines": { 545 | "node": ">=6.9.0" 546 | } 547 | }, 548 | "node_modules/@esbuild/aix-ppc64": { 549 | "version": "0.21.5", 550 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 551 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 552 | "cpu": [ 553 | "ppc64" 554 | ], 555 | "dev": true, 556 | "optional": true, 557 | "os": [ 558 | "aix" 559 | ], 560 | "engines": { 561 | "node": ">=12" 562 | } 563 | }, 564 | "node_modules/@esbuild/android-arm": { 565 | "version": "0.21.5", 566 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 567 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 568 | "cpu": [ 569 | "arm" 570 | ], 571 | "dev": true, 572 | "optional": true, 573 | "os": [ 574 | "android" 575 | ], 576 | "engines": { 577 | "node": ">=12" 578 | } 579 | }, 580 | "node_modules/@esbuild/android-arm64": { 581 | "version": "0.21.5", 582 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 583 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 584 | "cpu": [ 585 | "arm64" 586 | ], 587 | "dev": true, 588 | "optional": true, 589 | "os": [ 590 | "android" 591 | ], 592 | "engines": { 593 | "node": ">=12" 594 | } 595 | }, 596 | "node_modules/@esbuild/android-x64": { 597 | "version": "0.21.5", 598 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 599 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 600 | "cpu": [ 601 | "x64" 602 | ], 603 | "dev": true, 604 | "optional": true, 605 | "os": [ 606 | "android" 607 | ], 608 | "engines": { 609 | "node": ">=12" 610 | } 611 | }, 612 | "node_modules/@esbuild/darwin-arm64": { 613 | "version": "0.21.5", 614 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 615 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 616 | "cpu": [ 617 | "arm64" 618 | ], 619 | "dev": true, 620 | "optional": true, 621 | "os": [ 622 | "darwin" 623 | ], 624 | "engines": { 625 | "node": ">=12" 626 | } 627 | }, 628 | "node_modules/@esbuild/darwin-x64": { 629 | "version": "0.21.5", 630 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 631 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 632 | "cpu": [ 633 | "x64" 634 | ], 635 | "dev": true, 636 | "optional": true, 637 | "os": [ 638 | "darwin" 639 | ], 640 | "engines": { 641 | "node": ">=12" 642 | } 643 | }, 644 | "node_modules/@esbuild/freebsd-arm64": { 645 | "version": "0.21.5", 646 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 647 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 648 | "cpu": [ 649 | "arm64" 650 | ], 651 | "dev": true, 652 | "optional": true, 653 | "os": [ 654 | "freebsd" 655 | ], 656 | "engines": { 657 | "node": ">=12" 658 | } 659 | }, 660 | "node_modules/@esbuild/freebsd-x64": { 661 | "version": "0.21.5", 662 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 663 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 664 | "cpu": [ 665 | "x64" 666 | ], 667 | "dev": true, 668 | "optional": true, 669 | "os": [ 670 | "freebsd" 671 | ], 672 | "engines": { 673 | "node": ">=12" 674 | } 675 | }, 676 | "node_modules/@esbuild/linux-arm": { 677 | "version": "0.21.5", 678 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 679 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 680 | "cpu": [ 681 | "arm" 682 | ], 683 | "dev": true, 684 | "optional": true, 685 | "os": [ 686 | "linux" 687 | ], 688 | "engines": { 689 | "node": ">=12" 690 | } 691 | }, 692 | "node_modules/@esbuild/linux-arm64": { 693 | "version": "0.21.5", 694 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 695 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 696 | "cpu": [ 697 | "arm64" 698 | ], 699 | "dev": true, 700 | "optional": true, 701 | "os": [ 702 | "linux" 703 | ], 704 | "engines": { 705 | "node": ">=12" 706 | } 707 | }, 708 | "node_modules/@esbuild/linux-ia32": { 709 | "version": "0.21.5", 710 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 711 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 712 | "cpu": [ 713 | "ia32" 714 | ], 715 | "dev": true, 716 | "optional": true, 717 | "os": [ 718 | "linux" 719 | ], 720 | "engines": { 721 | "node": ">=12" 722 | } 723 | }, 724 | "node_modules/@esbuild/linux-loong64": { 725 | "version": "0.21.5", 726 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 727 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 728 | "cpu": [ 729 | "loong64" 730 | ], 731 | "dev": true, 732 | "optional": true, 733 | "os": [ 734 | "linux" 735 | ], 736 | "engines": { 737 | "node": ">=12" 738 | } 739 | }, 740 | "node_modules/@esbuild/linux-mips64el": { 741 | "version": "0.21.5", 742 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 743 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 744 | "cpu": [ 745 | "mips64el" 746 | ], 747 | "dev": true, 748 | "optional": true, 749 | "os": [ 750 | "linux" 751 | ], 752 | "engines": { 753 | "node": ">=12" 754 | } 755 | }, 756 | "node_modules/@esbuild/linux-ppc64": { 757 | "version": "0.21.5", 758 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 759 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 760 | "cpu": [ 761 | "ppc64" 762 | ], 763 | "dev": true, 764 | "optional": true, 765 | "os": [ 766 | "linux" 767 | ], 768 | "engines": { 769 | "node": ">=12" 770 | } 771 | }, 772 | "node_modules/@esbuild/linux-riscv64": { 773 | "version": "0.21.5", 774 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 775 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 776 | "cpu": [ 777 | "riscv64" 778 | ], 779 | "dev": true, 780 | "optional": true, 781 | "os": [ 782 | "linux" 783 | ], 784 | "engines": { 785 | "node": ">=12" 786 | } 787 | }, 788 | "node_modules/@esbuild/linux-s390x": { 789 | "version": "0.21.5", 790 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 791 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 792 | "cpu": [ 793 | "s390x" 794 | ], 795 | "dev": true, 796 | "optional": true, 797 | "os": [ 798 | "linux" 799 | ], 800 | "engines": { 801 | "node": ">=12" 802 | } 803 | }, 804 | "node_modules/@esbuild/linux-x64": { 805 | "version": "0.21.5", 806 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 807 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 808 | "cpu": [ 809 | "x64" 810 | ], 811 | "dev": true, 812 | "optional": true, 813 | "os": [ 814 | "linux" 815 | ], 816 | "engines": { 817 | "node": ">=12" 818 | } 819 | }, 820 | "node_modules/@esbuild/netbsd-x64": { 821 | "version": "0.21.5", 822 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 823 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 824 | "cpu": [ 825 | "x64" 826 | ], 827 | "dev": true, 828 | "optional": true, 829 | "os": [ 830 | "netbsd" 831 | ], 832 | "engines": { 833 | "node": ">=12" 834 | } 835 | }, 836 | "node_modules/@esbuild/openbsd-x64": { 837 | "version": "0.21.5", 838 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 839 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 840 | "cpu": [ 841 | "x64" 842 | ], 843 | "dev": true, 844 | "optional": true, 845 | "os": [ 846 | "openbsd" 847 | ], 848 | "engines": { 849 | "node": ">=12" 850 | } 851 | }, 852 | "node_modules/@esbuild/sunos-x64": { 853 | "version": "0.21.5", 854 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 855 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 856 | "cpu": [ 857 | "x64" 858 | ], 859 | "dev": true, 860 | "optional": true, 861 | "os": [ 862 | "sunos" 863 | ], 864 | "engines": { 865 | "node": ">=12" 866 | } 867 | }, 868 | "node_modules/@esbuild/win32-arm64": { 869 | "version": "0.21.5", 870 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 871 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 872 | "cpu": [ 873 | "arm64" 874 | ], 875 | "dev": true, 876 | "optional": true, 877 | "os": [ 878 | "win32" 879 | ], 880 | "engines": { 881 | "node": ">=12" 882 | } 883 | }, 884 | "node_modules/@esbuild/win32-ia32": { 885 | "version": "0.21.5", 886 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 887 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 888 | "cpu": [ 889 | "ia32" 890 | ], 891 | "dev": true, 892 | "optional": true, 893 | "os": [ 894 | "win32" 895 | ], 896 | "engines": { 897 | "node": ">=12" 898 | } 899 | }, 900 | "node_modules/@esbuild/win32-x64": { 901 | "version": "0.21.5", 902 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 903 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 904 | "cpu": [ 905 | "x64" 906 | ], 907 | "dev": true, 908 | "optional": true, 909 | "os": [ 910 | "win32" 911 | ], 912 | "engines": { 913 | "node": ">=12" 914 | } 915 | }, 916 | "node_modules/@jridgewell/gen-mapping": { 917 | "version": "0.3.5", 918 | "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", 919 | "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", 920 | "dev": true, 921 | "dependencies": { 922 | "@jridgewell/set-array": "^1.2.1", 923 | "@jridgewell/sourcemap-codec": "^1.4.10", 924 | "@jridgewell/trace-mapping": "^0.3.24" 925 | }, 926 | "engines": { 927 | "node": ">=6.0.0" 928 | } 929 | }, 930 | "node_modules/@jridgewell/resolve-uri": { 931 | "version": "3.1.2", 932 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 933 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 934 | "dev": true, 935 | "engines": { 936 | "node": ">=6.0.0" 937 | } 938 | }, 939 | "node_modules/@jridgewell/set-array": { 940 | "version": "1.2.1", 941 | "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", 942 | "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", 943 | "dev": true, 944 | "engines": { 945 | "node": ">=6.0.0" 946 | } 947 | }, 948 | "node_modules/@jridgewell/sourcemap-codec": { 949 | "version": "1.5.0", 950 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 951 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" 952 | }, 953 | "node_modules/@jridgewell/trace-mapping": { 954 | "version": "0.3.25", 955 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", 956 | "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", 957 | "dev": true, 958 | "dependencies": { 959 | "@jridgewell/resolve-uri": "^3.1.0", 960 | "@jridgewell/sourcemap-codec": "^1.4.14" 961 | } 962 | }, 963 | "node_modules/@kurkle/color": { 964 | "version": "0.3.2", 965 | "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", 966 | "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" 967 | }, 968 | "node_modules/@polka/url": { 969 | "version": "1.0.0-next.28", 970 | "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", 971 | "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", 972 | "dev": true 973 | }, 974 | "node_modules/@rollup/pluginutils": { 975 | "version": "5.1.3", 976 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", 977 | "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", 978 | "dev": true, 979 | "dependencies": { 980 | "@types/estree": "^1.0.0", 981 | "estree-walker": "^2.0.2", 982 | "picomatch": "^4.0.2" 983 | }, 984 | "engines": { 985 | "node": ">=14.0.0" 986 | }, 987 | "peerDependencies": { 988 | "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 989 | }, 990 | "peerDependenciesMeta": { 991 | "rollup": { 992 | "optional": true 993 | } 994 | } 995 | }, 996 | "node_modules/@rollup/rollup-android-arm-eabi": { 997 | "version": "4.24.0", 998 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz", 999 | "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==", 1000 | "cpu": [ 1001 | "arm" 1002 | ], 1003 | "dev": true, 1004 | "optional": true, 1005 | "os": [ 1006 | "android" 1007 | ] 1008 | }, 1009 | "node_modules/@rollup/rollup-android-arm64": { 1010 | "version": "4.24.0", 1011 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz", 1012 | "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==", 1013 | "cpu": [ 1014 | "arm64" 1015 | ], 1016 | "dev": true, 1017 | "optional": true, 1018 | "os": [ 1019 | "android" 1020 | ] 1021 | }, 1022 | "node_modules/@rollup/rollup-darwin-arm64": { 1023 | "version": "4.24.0", 1024 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz", 1025 | "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==", 1026 | "cpu": [ 1027 | "arm64" 1028 | ], 1029 | "dev": true, 1030 | "optional": true, 1031 | "os": [ 1032 | "darwin" 1033 | ] 1034 | }, 1035 | "node_modules/@rollup/rollup-darwin-x64": { 1036 | "version": "4.24.0", 1037 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz", 1038 | "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==", 1039 | "cpu": [ 1040 | "x64" 1041 | ], 1042 | "dev": true, 1043 | "optional": true, 1044 | "os": [ 1045 | "darwin" 1046 | ] 1047 | }, 1048 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 1049 | "version": "4.24.0", 1050 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz", 1051 | "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==", 1052 | "cpu": [ 1053 | "arm" 1054 | ], 1055 | "dev": true, 1056 | "optional": true, 1057 | "os": [ 1058 | "linux" 1059 | ] 1060 | }, 1061 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 1062 | "version": "4.24.0", 1063 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz", 1064 | "integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==", 1065 | "cpu": [ 1066 | "arm" 1067 | ], 1068 | "dev": true, 1069 | "optional": true, 1070 | "os": [ 1071 | "linux" 1072 | ] 1073 | }, 1074 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 1075 | "version": "4.24.0", 1076 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz", 1077 | "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==", 1078 | "cpu": [ 1079 | "arm64" 1080 | ], 1081 | "dev": true, 1082 | "optional": true, 1083 | "os": [ 1084 | "linux" 1085 | ] 1086 | }, 1087 | "node_modules/@rollup/rollup-linux-arm64-musl": { 1088 | "version": "4.24.0", 1089 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz", 1090 | "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==", 1091 | "cpu": [ 1092 | "arm64" 1093 | ], 1094 | "dev": true, 1095 | "optional": true, 1096 | "os": [ 1097 | "linux" 1098 | ] 1099 | }, 1100 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 1101 | "version": "4.24.0", 1102 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz", 1103 | "integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==", 1104 | "cpu": [ 1105 | "ppc64" 1106 | ], 1107 | "dev": true, 1108 | "optional": true, 1109 | "os": [ 1110 | "linux" 1111 | ] 1112 | }, 1113 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 1114 | "version": "4.24.0", 1115 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz", 1116 | "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==", 1117 | "cpu": [ 1118 | "riscv64" 1119 | ], 1120 | "dev": true, 1121 | "optional": true, 1122 | "os": [ 1123 | "linux" 1124 | ] 1125 | }, 1126 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 1127 | "version": "4.24.0", 1128 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz", 1129 | "integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==", 1130 | "cpu": [ 1131 | "s390x" 1132 | ], 1133 | "dev": true, 1134 | "optional": true, 1135 | "os": [ 1136 | "linux" 1137 | ] 1138 | }, 1139 | "node_modules/@rollup/rollup-linux-x64-gnu": { 1140 | "version": "4.24.0", 1141 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz", 1142 | "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==", 1143 | "cpu": [ 1144 | "x64" 1145 | ], 1146 | "dev": true, 1147 | "optional": true, 1148 | "os": [ 1149 | "linux" 1150 | ] 1151 | }, 1152 | "node_modules/@rollup/rollup-linux-x64-musl": { 1153 | "version": "4.24.0", 1154 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz", 1155 | "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==", 1156 | "cpu": [ 1157 | "x64" 1158 | ], 1159 | "dev": true, 1160 | "optional": true, 1161 | "os": [ 1162 | "linux" 1163 | ] 1164 | }, 1165 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 1166 | "version": "4.24.0", 1167 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz", 1168 | "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==", 1169 | "cpu": [ 1170 | "arm64" 1171 | ], 1172 | "dev": true, 1173 | "optional": true, 1174 | "os": [ 1175 | "win32" 1176 | ] 1177 | }, 1178 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 1179 | "version": "4.24.0", 1180 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz", 1181 | "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==", 1182 | "cpu": [ 1183 | "ia32" 1184 | ], 1185 | "dev": true, 1186 | "optional": true, 1187 | "os": [ 1188 | "win32" 1189 | ] 1190 | }, 1191 | "node_modules/@rollup/rollup-win32-x64-msvc": { 1192 | "version": "4.24.0", 1193 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz", 1194 | "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==", 1195 | "cpu": [ 1196 | "x64" 1197 | ], 1198 | "dev": true, 1199 | "optional": true, 1200 | "os": [ 1201 | "win32" 1202 | ] 1203 | }, 1204 | "node_modules/@types/estree": { 1205 | "version": "1.0.6", 1206 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 1207 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 1208 | "dev": true 1209 | }, 1210 | "node_modules/@vitejs/plugin-vue": { 1211 | "version": "5.1.4", 1212 | "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.4.tgz", 1213 | "integrity": "sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==", 1214 | "dev": true, 1215 | "engines": { 1216 | "node": "^18.0.0 || >=20.0.0" 1217 | }, 1218 | "peerDependencies": { 1219 | "vite": "^5.0.0", 1220 | "vue": "^3.2.25" 1221 | } 1222 | }, 1223 | "node_modules/@vue/babel-helper-vue-transform-on": { 1224 | "version": "1.2.5", 1225 | "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz", 1226 | "integrity": "sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==", 1227 | "dev": true 1228 | }, 1229 | "node_modules/@vue/babel-plugin-jsx": { 1230 | "version": "1.2.5", 1231 | "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.5.tgz", 1232 | "integrity": "sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==", 1233 | "dev": true, 1234 | "dependencies": { 1235 | "@babel/helper-module-imports": "^7.24.7", 1236 | "@babel/helper-plugin-utils": "^7.24.8", 1237 | "@babel/plugin-syntax-jsx": "^7.24.7", 1238 | "@babel/template": "^7.25.0", 1239 | "@babel/traverse": "^7.25.6", 1240 | "@babel/types": "^7.25.6", 1241 | "@vue/babel-helper-vue-transform-on": "1.2.5", 1242 | "@vue/babel-plugin-resolve-type": "1.2.5", 1243 | "html-tags": "^3.3.1", 1244 | "svg-tags": "^1.0.0" 1245 | }, 1246 | "peerDependencies": { 1247 | "@babel/core": "^7.0.0-0" 1248 | }, 1249 | "peerDependenciesMeta": { 1250 | "@babel/core": { 1251 | "optional": true 1252 | } 1253 | } 1254 | }, 1255 | "node_modules/@vue/babel-plugin-resolve-type": { 1256 | "version": "1.2.5", 1257 | "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.5.tgz", 1258 | "integrity": "sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==", 1259 | "dev": true, 1260 | "dependencies": { 1261 | "@babel/code-frame": "^7.24.7", 1262 | "@babel/helper-module-imports": "^7.24.7", 1263 | "@babel/helper-plugin-utils": "^7.24.8", 1264 | "@babel/parser": "^7.25.6", 1265 | "@vue/compiler-sfc": "^3.5.3" 1266 | }, 1267 | "peerDependencies": { 1268 | "@babel/core": "^7.0.0-0" 1269 | } 1270 | }, 1271 | "node_modules/@vue/compiler-core": { 1272 | "version": "3.5.12", 1273 | "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", 1274 | "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", 1275 | "dependencies": { 1276 | "@babel/parser": "^7.25.3", 1277 | "@vue/shared": "3.5.12", 1278 | "entities": "^4.5.0", 1279 | "estree-walker": "^2.0.2", 1280 | "source-map-js": "^1.2.0" 1281 | } 1282 | }, 1283 | "node_modules/@vue/compiler-dom": { 1284 | "version": "3.5.12", 1285 | "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", 1286 | "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", 1287 | "dependencies": { 1288 | "@vue/compiler-core": "3.5.12", 1289 | "@vue/shared": "3.5.12" 1290 | } 1291 | }, 1292 | "node_modules/@vue/compiler-sfc": { 1293 | "version": "3.5.12", 1294 | "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", 1295 | "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", 1296 | "dependencies": { 1297 | "@babel/parser": "^7.25.3", 1298 | "@vue/compiler-core": "3.5.12", 1299 | "@vue/compiler-dom": "3.5.12", 1300 | "@vue/compiler-ssr": "3.5.12", 1301 | "@vue/shared": "3.5.12", 1302 | "estree-walker": "^2.0.2", 1303 | "magic-string": "^0.30.11", 1304 | "postcss": "^8.4.47", 1305 | "source-map-js": "^1.2.0" 1306 | } 1307 | }, 1308 | "node_modules/@vue/compiler-ssr": { 1309 | "version": "3.5.12", 1310 | "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", 1311 | "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", 1312 | "dependencies": { 1313 | "@vue/compiler-dom": "3.5.12", 1314 | "@vue/shared": "3.5.12" 1315 | } 1316 | }, 1317 | "node_modules/@vue/devtools-core": { 1318 | "version": "7.5.3", 1319 | "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.5.3.tgz", 1320 | "integrity": "sha512-KIzT76GPubhvvQzjQtf7yhaSdVOcq/0CV2QbUw2bf3vzV+biHsgfgQ1XEbPbPKuHr1t0zshj53mBv0bx2OmkJw==", 1321 | "dev": true, 1322 | "dependencies": { 1323 | "@vue/devtools-kit": "^7.5.3", 1324 | "@vue/devtools-shared": "^7.5.3", 1325 | "mitt": "^3.0.1", 1326 | "nanoid": "^3.3.4", 1327 | "pathe": "^1.1.2", 1328 | "vite-hot-client": "^0.2.3" 1329 | }, 1330 | "peerDependencies": { 1331 | "vue": "^3.0.0" 1332 | } 1333 | }, 1334 | "node_modules/@vue/devtools-kit": { 1335 | "version": "7.5.3", 1336 | "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.5.3.tgz", 1337 | "integrity": "sha512-XSTXCAHshYniK3gLQfhMRDuDLLj6vHFWKVl1tvtSgZ0iJy5AXoI4U/GKGlyS2uS1hwZCSoNSGdkKtbW/pn/Iuw==", 1338 | "dev": true, 1339 | "dependencies": { 1340 | "@vue/devtools-shared": "^7.5.3", 1341 | "birpc": "^0.2.19", 1342 | "hookable": "^5.5.3", 1343 | "mitt": "^3.0.1", 1344 | "perfect-debounce": "^1.0.0", 1345 | "speakingurl": "^14.0.1", 1346 | "superjson": "^2.2.1" 1347 | } 1348 | }, 1349 | "node_modules/@vue/devtools-shared": { 1350 | "version": "7.5.3", 1351 | "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.5.3.tgz", 1352 | "integrity": "sha512-i2tCUtAEQ0S8AmTuy6FSOmVKCB5ajmMaVrrw0ypX75koLSo1mssQ8zezds5IoUZHRiXBsgoGHbJGuGwyrSGhqQ==", 1353 | "dev": true, 1354 | "dependencies": { 1355 | "rfdc": "^1.4.1" 1356 | } 1357 | }, 1358 | "node_modules/@vue/reactivity": { 1359 | "version": "3.5.12", 1360 | "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", 1361 | "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", 1362 | "dependencies": { 1363 | "@vue/shared": "3.5.12" 1364 | } 1365 | }, 1366 | "node_modules/@vue/runtime-core": { 1367 | "version": "3.5.12", 1368 | "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", 1369 | "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", 1370 | "dependencies": { 1371 | "@vue/reactivity": "3.5.12", 1372 | "@vue/shared": "3.5.12" 1373 | } 1374 | }, 1375 | "node_modules/@vue/runtime-dom": { 1376 | "version": "3.5.12", 1377 | "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", 1378 | "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", 1379 | "dependencies": { 1380 | "@vue/reactivity": "3.5.12", 1381 | "@vue/runtime-core": "3.5.12", 1382 | "@vue/shared": "3.5.12", 1383 | "csstype": "^3.1.3" 1384 | } 1385 | }, 1386 | "node_modules/@vue/server-renderer": { 1387 | "version": "3.5.12", 1388 | "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", 1389 | "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", 1390 | "dependencies": { 1391 | "@vue/compiler-ssr": "3.5.12", 1392 | "@vue/shared": "3.5.12" 1393 | }, 1394 | "peerDependencies": { 1395 | "vue": "3.5.12" 1396 | } 1397 | }, 1398 | "node_modules/@vue/shared": { 1399 | "version": "3.5.12", 1400 | "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", 1401 | "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==" 1402 | }, 1403 | "node_modules/ansi-regex": { 1404 | "version": "5.0.1", 1405 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 1406 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 1407 | "dev": true, 1408 | "engines": { 1409 | "node": ">=8" 1410 | } 1411 | }, 1412 | "node_modules/ansi-styles": { 1413 | "version": "4.3.0", 1414 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 1415 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 1416 | "dev": true, 1417 | "dependencies": { 1418 | "color-convert": "^2.0.1" 1419 | }, 1420 | "engines": { 1421 | "node": ">=8" 1422 | }, 1423 | "funding": { 1424 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 1425 | } 1426 | }, 1427 | "node_modules/birpc": { 1428 | "version": "0.2.19", 1429 | "resolved": "https://registry.npmjs.org/birpc/-/birpc-0.2.19.tgz", 1430 | "integrity": "sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==", 1431 | "dev": true, 1432 | "funding": { 1433 | "url": "https://github.com/sponsors/antfu" 1434 | } 1435 | }, 1436 | "node_modules/browserslist": { 1437 | "version": "4.24.2", 1438 | "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", 1439 | "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", 1440 | "dev": true, 1441 | "funding": [ 1442 | { 1443 | "type": "opencollective", 1444 | "url": "https://opencollective.com/browserslist" 1445 | }, 1446 | { 1447 | "type": "tidelift", 1448 | "url": "https://tidelift.com/funding/github/npm/browserslist" 1449 | }, 1450 | { 1451 | "type": "github", 1452 | "url": "https://github.com/sponsors/ai" 1453 | } 1454 | ], 1455 | "dependencies": { 1456 | "caniuse-lite": "^1.0.30001669", 1457 | "electron-to-chromium": "^1.5.41", 1458 | "node-releases": "^2.0.18", 1459 | "update-browserslist-db": "^1.1.1" 1460 | }, 1461 | "bin": { 1462 | "browserslist": "cli.js" 1463 | }, 1464 | "engines": { 1465 | "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 1466 | } 1467 | }, 1468 | "node_modules/bundle-name": { 1469 | "version": "4.1.0", 1470 | "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", 1471 | "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", 1472 | "dev": true, 1473 | "dependencies": { 1474 | "run-applescript": "^7.0.0" 1475 | }, 1476 | "engines": { 1477 | "node": ">=18" 1478 | }, 1479 | "funding": { 1480 | "url": "https://github.com/sponsors/sindresorhus" 1481 | } 1482 | }, 1483 | "node_modules/caniuse-lite": { 1484 | "version": "1.0.30001669", 1485 | "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", 1486 | "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", 1487 | "dev": true, 1488 | "funding": [ 1489 | { 1490 | "type": "opencollective", 1491 | "url": "https://opencollective.com/browserslist" 1492 | }, 1493 | { 1494 | "type": "tidelift", 1495 | "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 1496 | }, 1497 | { 1498 | "type": "github", 1499 | "url": "https://github.com/sponsors/ai" 1500 | } 1501 | ] 1502 | }, 1503 | "node_modules/chalk": { 1504 | "version": "4.1.2", 1505 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 1506 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 1507 | "dev": true, 1508 | "dependencies": { 1509 | "ansi-styles": "^4.1.0", 1510 | "supports-color": "^7.1.0" 1511 | }, 1512 | "engines": { 1513 | "node": ">=10" 1514 | }, 1515 | "funding": { 1516 | "url": "https://github.com/chalk/chalk?sponsor=1" 1517 | } 1518 | }, 1519 | "node_modules/chalk/node_modules/supports-color": { 1520 | "version": "7.2.0", 1521 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1522 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1523 | "dev": true, 1524 | "dependencies": { 1525 | "has-flag": "^4.0.0" 1526 | }, 1527 | "engines": { 1528 | "node": ">=8" 1529 | } 1530 | }, 1531 | "node_modules/chart.js": { 1532 | "version": "4.4.5", 1533 | "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.5.tgz", 1534 | "integrity": "sha512-CVVjg1RYTJV9OCC8WeJPMx8gsV8K6WIyIEQUE3ui4AR9Hfgls9URri6Ja3hyMVBbTF8Q2KFa19PE815gWcWhng==", 1535 | "dependencies": { 1536 | "@kurkle/color": "^0.3.0" 1537 | }, 1538 | "engines": { 1539 | "pnpm": ">=8" 1540 | } 1541 | }, 1542 | "node_modules/cliui": { 1543 | "version": "8.0.1", 1544 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", 1545 | "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", 1546 | "dev": true, 1547 | "dependencies": { 1548 | "string-width": "^4.2.0", 1549 | "strip-ansi": "^6.0.1", 1550 | "wrap-ansi": "^7.0.0" 1551 | }, 1552 | "engines": { 1553 | "node": ">=12" 1554 | } 1555 | }, 1556 | "node_modules/color-convert": { 1557 | "version": "2.0.1", 1558 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 1559 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 1560 | "dev": true, 1561 | "dependencies": { 1562 | "color-name": "~1.1.4" 1563 | }, 1564 | "engines": { 1565 | "node": ">=7.0.0" 1566 | } 1567 | }, 1568 | "node_modules/color-name": { 1569 | "version": "1.1.4", 1570 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 1571 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 1572 | "dev": true 1573 | }, 1574 | "node_modules/concurrently": { 1575 | "version": "9.0.1", 1576 | "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.0.1.tgz", 1577 | "integrity": "sha512-wYKvCd/f54sTXJMSfV6Ln/B8UrfLBKOYa+lzc6CHay3Qek+LorVSBdMVfyewFhRbH0Rbabsk4D+3PL/VjQ5gzg==", 1578 | "dev": true, 1579 | "dependencies": { 1580 | "chalk": "^4.1.2", 1581 | "lodash": "^4.17.21", 1582 | "rxjs": "^7.8.1", 1583 | "shell-quote": "^1.8.1", 1584 | "supports-color": "^8.1.1", 1585 | "tree-kill": "^1.2.2", 1586 | "yargs": "^17.7.2" 1587 | }, 1588 | "bin": { 1589 | "conc": "dist/bin/concurrently.js", 1590 | "concurrently": "dist/bin/concurrently.js" 1591 | }, 1592 | "engines": { 1593 | "node": ">=18" 1594 | }, 1595 | "funding": { 1596 | "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" 1597 | } 1598 | }, 1599 | "node_modules/convert-source-map": { 1600 | "version": "2.0.0", 1601 | "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", 1602 | "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", 1603 | "dev": true 1604 | }, 1605 | "node_modules/copy-anything": { 1606 | "version": "3.0.5", 1607 | "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-3.0.5.tgz", 1608 | "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", 1609 | "dev": true, 1610 | "dependencies": { 1611 | "is-what": "^4.1.8" 1612 | }, 1613 | "engines": { 1614 | "node": ">=12.13" 1615 | }, 1616 | "funding": { 1617 | "url": "https://github.com/sponsors/mesqueeb" 1618 | } 1619 | }, 1620 | "node_modules/cross-spawn": { 1621 | "version": "7.0.3", 1622 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", 1623 | "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", 1624 | "dev": true, 1625 | "dependencies": { 1626 | "path-key": "^3.1.0", 1627 | "shebang-command": "^2.0.0", 1628 | "which": "^2.0.1" 1629 | }, 1630 | "engines": { 1631 | "node": ">= 8" 1632 | } 1633 | }, 1634 | "node_modules/csstype": { 1635 | "version": "3.1.3", 1636 | "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", 1637 | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" 1638 | }, 1639 | "node_modules/debug": { 1640 | "version": "4.3.7", 1641 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", 1642 | "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", 1643 | "dev": true, 1644 | "dependencies": { 1645 | "ms": "^2.1.3" 1646 | }, 1647 | "engines": { 1648 | "node": ">=6.0" 1649 | }, 1650 | "peerDependenciesMeta": { 1651 | "supports-color": { 1652 | "optional": true 1653 | } 1654 | } 1655 | }, 1656 | "node_modules/default-browser": { 1657 | "version": "5.2.1", 1658 | "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", 1659 | "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", 1660 | "dev": true, 1661 | "dependencies": { 1662 | "bundle-name": "^4.1.0", 1663 | "default-browser-id": "^5.0.0" 1664 | }, 1665 | "engines": { 1666 | "node": ">=18" 1667 | }, 1668 | "funding": { 1669 | "url": "https://github.com/sponsors/sindresorhus" 1670 | } 1671 | }, 1672 | "node_modules/default-browser-id": { 1673 | "version": "5.0.0", 1674 | "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", 1675 | "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", 1676 | "dev": true, 1677 | "engines": { 1678 | "node": ">=18" 1679 | }, 1680 | "funding": { 1681 | "url": "https://github.com/sponsors/sindresorhus" 1682 | } 1683 | }, 1684 | "node_modules/define-lazy-prop": { 1685 | "version": "3.0.0", 1686 | "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", 1687 | "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", 1688 | "dev": true, 1689 | "engines": { 1690 | "node": ">=12" 1691 | }, 1692 | "funding": { 1693 | "url": "https://github.com/sponsors/sindresorhus" 1694 | } 1695 | }, 1696 | "node_modules/electron-to-chromium": { 1697 | "version": "1.5.44", 1698 | "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.44.tgz", 1699 | "integrity": "sha512-Lz3POUa7wANQA8G+9btKAdH+cqkfWCBdkotvQZJVOqRXMYGm1tTD835Z01iCjWpEBf0RInPBWuPfzhGbxOCULw==", 1700 | "dev": true 1701 | }, 1702 | "node_modules/emoji-regex": { 1703 | "version": "8.0.0", 1704 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 1705 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 1706 | "dev": true 1707 | }, 1708 | "node_modules/entities": { 1709 | "version": "4.5.0", 1710 | "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", 1711 | "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", 1712 | "engines": { 1713 | "node": ">=0.12" 1714 | }, 1715 | "funding": { 1716 | "url": "https://github.com/fb55/entities?sponsor=1" 1717 | } 1718 | }, 1719 | "node_modules/error-stack-parser-es": { 1720 | "version": "0.1.5", 1721 | "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", 1722 | "integrity": "sha512-xHku1X40RO+fO8yJ8Wh2f2rZWVjqyhb1zgq1yZ8aZRQkv6OOKhKWRUaht3eSCUbAOBaKIgM+ykwFLE+QUxgGeg==", 1723 | "dev": true, 1724 | "funding": { 1725 | "url": "https://github.com/sponsors/antfu" 1726 | } 1727 | }, 1728 | "node_modules/esbuild": { 1729 | "version": "0.21.5", 1730 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 1731 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 1732 | "dev": true, 1733 | "hasInstallScript": true, 1734 | "bin": { 1735 | "esbuild": "bin/esbuild" 1736 | }, 1737 | "engines": { 1738 | "node": ">=12" 1739 | }, 1740 | "optionalDependencies": { 1741 | "@esbuild/aix-ppc64": "0.21.5", 1742 | "@esbuild/android-arm": "0.21.5", 1743 | "@esbuild/android-arm64": "0.21.5", 1744 | "@esbuild/android-x64": "0.21.5", 1745 | "@esbuild/darwin-arm64": "0.21.5", 1746 | "@esbuild/darwin-x64": "0.21.5", 1747 | "@esbuild/freebsd-arm64": "0.21.5", 1748 | "@esbuild/freebsd-x64": "0.21.5", 1749 | "@esbuild/linux-arm": "0.21.5", 1750 | "@esbuild/linux-arm64": "0.21.5", 1751 | "@esbuild/linux-ia32": "0.21.5", 1752 | "@esbuild/linux-loong64": "0.21.5", 1753 | "@esbuild/linux-mips64el": "0.21.5", 1754 | "@esbuild/linux-ppc64": "0.21.5", 1755 | "@esbuild/linux-riscv64": "0.21.5", 1756 | "@esbuild/linux-s390x": "0.21.5", 1757 | "@esbuild/linux-x64": "0.21.5", 1758 | "@esbuild/netbsd-x64": "0.21.5", 1759 | "@esbuild/openbsd-x64": "0.21.5", 1760 | "@esbuild/sunos-x64": "0.21.5", 1761 | "@esbuild/win32-arm64": "0.21.5", 1762 | "@esbuild/win32-ia32": "0.21.5", 1763 | "@esbuild/win32-x64": "0.21.5" 1764 | } 1765 | }, 1766 | "node_modules/escalade": { 1767 | "version": "3.2.0", 1768 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 1769 | "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 1770 | "dev": true, 1771 | "engines": { 1772 | "node": ">=6" 1773 | } 1774 | }, 1775 | "node_modules/escape-string-regexp": { 1776 | "version": "1.0.5", 1777 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 1778 | "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", 1779 | "dev": true, 1780 | "engines": { 1781 | "node": ">=0.8.0" 1782 | } 1783 | }, 1784 | "node_modules/estree-walker": { 1785 | "version": "2.0.2", 1786 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 1787 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" 1788 | }, 1789 | "node_modules/execa": { 1790 | "version": "8.0.1", 1791 | "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", 1792 | "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", 1793 | "dev": true, 1794 | "dependencies": { 1795 | "cross-spawn": "^7.0.3", 1796 | "get-stream": "^8.0.1", 1797 | "human-signals": "^5.0.0", 1798 | "is-stream": "^3.0.0", 1799 | "merge-stream": "^2.0.0", 1800 | "npm-run-path": "^5.1.0", 1801 | "onetime": "^6.0.0", 1802 | "signal-exit": "^4.1.0", 1803 | "strip-final-newline": "^3.0.0" 1804 | }, 1805 | "engines": { 1806 | "node": ">=16.17" 1807 | }, 1808 | "funding": { 1809 | "url": "https://github.com/sindresorhus/execa?sponsor=1" 1810 | } 1811 | }, 1812 | "node_modules/fs-extra": { 1813 | "version": "11.2.0", 1814 | "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", 1815 | "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", 1816 | "dev": true, 1817 | "dependencies": { 1818 | "graceful-fs": "^4.2.0", 1819 | "jsonfile": "^6.0.1", 1820 | "universalify": "^2.0.0" 1821 | }, 1822 | "engines": { 1823 | "node": ">=14.14" 1824 | } 1825 | }, 1826 | "node_modules/fsevents": { 1827 | "version": "2.3.3", 1828 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1829 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1830 | "dev": true, 1831 | "hasInstallScript": true, 1832 | "optional": true, 1833 | "os": [ 1834 | "darwin" 1835 | ], 1836 | "engines": { 1837 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 1838 | } 1839 | }, 1840 | "node_modules/gensync": { 1841 | "version": "1.0.0-beta.2", 1842 | "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", 1843 | "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", 1844 | "dev": true, 1845 | "engines": { 1846 | "node": ">=6.9.0" 1847 | } 1848 | }, 1849 | "node_modules/get-caller-file": { 1850 | "version": "2.0.5", 1851 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 1852 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 1853 | "dev": true, 1854 | "engines": { 1855 | "node": "6.* || 8.* || >= 10.*" 1856 | } 1857 | }, 1858 | "node_modules/get-stream": { 1859 | "version": "8.0.1", 1860 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", 1861 | "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", 1862 | "dev": true, 1863 | "engines": { 1864 | "node": ">=16" 1865 | }, 1866 | "funding": { 1867 | "url": "https://github.com/sponsors/sindresorhus" 1868 | } 1869 | }, 1870 | "node_modules/globals": { 1871 | "version": "11.12.0", 1872 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 1873 | "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 1874 | "dev": true, 1875 | "engines": { 1876 | "node": ">=4" 1877 | } 1878 | }, 1879 | "node_modules/graceful-fs": { 1880 | "version": "4.2.11", 1881 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", 1882 | "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", 1883 | "dev": true 1884 | }, 1885 | "node_modules/has-flag": { 1886 | "version": "4.0.0", 1887 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1888 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1889 | "dev": true, 1890 | "engines": { 1891 | "node": ">=8" 1892 | } 1893 | }, 1894 | "node_modules/hookable": { 1895 | "version": "5.5.3", 1896 | "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", 1897 | "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", 1898 | "dev": true 1899 | }, 1900 | "node_modules/html-tags": { 1901 | "version": "3.3.1", 1902 | "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", 1903 | "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", 1904 | "dev": true, 1905 | "engines": { 1906 | "node": ">=8" 1907 | }, 1908 | "funding": { 1909 | "url": "https://github.com/sponsors/sindresorhus" 1910 | } 1911 | }, 1912 | "node_modules/human-signals": { 1913 | "version": "5.0.0", 1914 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", 1915 | "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", 1916 | "dev": true, 1917 | "engines": { 1918 | "node": ">=16.17.0" 1919 | } 1920 | }, 1921 | "node_modules/is-docker": { 1922 | "version": "3.0.0", 1923 | "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", 1924 | "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", 1925 | "dev": true, 1926 | "bin": { 1927 | "is-docker": "cli.js" 1928 | }, 1929 | "engines": { 1930 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1931 | }, 1932 | "funding": { 1933 | "url": "https://github.com/sponsors/sindresorhus" 1934 | } 1935 | }, 1936 | "node_modules/is-fullwidth-code-point": { 1937 | "version": "3.0.0", 1938 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 1939 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 1940 | "dev": true, 1941 | "engines": { 1942 | "node": ">=8" 1943 | } 1944 | }, 1945 | "node_modules/is-inside-container": { 1946 | "version": "1.0.0", 1947 | "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", 1948 | "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", 1949 | "dev": true, 1950 | "dependencies": { 1951 | "is-docker": "^3.0.0" 1952 | }, 1953 | "bin": { 1954 | "is-inside-container": "cli.js" 1955 | }, 1956 | "engines": { 1957 | "node": ">=14.16" 1958 | }, 1959 | "funding": { 1960 | "url": "https://github.com/sponsors/sindresorhus" 1961 | } 1962 | }, 1963 | "node_modules/is-stream": { 1964 | "version": "3.0.0", 1965 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", 1966 | "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", 1967 | "dev": true, 1968 | "engines": { 1969 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1970 | }, 1971 | "funding": { 1972 | "url": "https://github.com/sponsors/sindresorhus" 1973 | } 1974 | }, 1975 | "node_modules/is-what": { 1976 | "version": "4.1.16", 1977 | "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz", 1978 | "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", 1979 | "dev": true, 1980 | "engines": { 1981 | "node": ">=12.13" 1982 | }, 1983 | "funding": { 1984 | "url": "https://github.com/sponsors/mesqueeb" 1985 | } 1986 | }, 1987 | "node_modules/is-wsl": { 1988 | "version": "3.1.0", 1989 | "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", 1990 | "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", 1991 | "dev": true, 1992 | "dependencies": { 1993 | "is-inside-container": "^1.0.0" 1994 | }, 1995 | "engines": { 1996 | "node": ">=16" 1997 | }, 1998 | "funding": { 1999 | "url": "https://github.com/sponsors/sindresorhus" 2000 | } 2001 | }, 2002 | "node_modules/isexe": { 2003 | "version": "2.0.0", 2004 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 2005 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 2006 | "dev": true 2007 | }, 2008 | "node_modules/js-tokens": { 2009 | "version": "4.0.0", 2010 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 2011 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 2012 | "dev": true 2013 | }, 2014 | "node_modules/jsesc": { 2015 | "version": "3.0.2", 2016 | "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", 2017 | "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", 2018 | "dev": true, 2019 | "bin": { 2020 | "jsesc": "bin/jsesc" 2021 | }, 2022 | "engines": { 2023 | "node": ">=6" 2024 | } 2025 | }, 2026 | "node_modules/json5": { 2027 | "version": "2.2.3", 2028 | "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", 2029 | "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", 2030 | "dev": true, 2031 | "bin": { 2032 | "json5": "lib/cli.js" 2033 | }, 2034 | "engines": { 2035 | "node": ">=6" 2036 | } 2037 | }, 2038 | "node_modules/jsonfile": { 2039 | "version": "6.1.0", 2040 | "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", 2041 | "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", 2042 | "dev": true, 2043 | "dependencies": { 2044 | "universalify": "^2.0.0" 2045 | }, 2046 | "optionalDependencies": { 2047 | "graceful-fs": "^4.1.6" 2048 | } 2049 | }, 2050 | "node_modules/kolorist": { 2051 | "version": "1.8.0", 2052 | "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", 2053 | "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", 2054 | "dev": true 2055 | }, 2056 | "node_modules/lodash": { 2057 | "version": "4.17.21", 2058 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 2059 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 2060 | "dev": true 2061 | }, 2062 | "node_modules/lru-cache": { 2063 | "version": "5.1.1", 2064 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", 2065 | "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", 2066 | "dev": true, 2067 | "dependencies": { 2068 | "yallist": "^3.0.2" 2069 | } 2070 | }, 2071 | "node_modules/magic-string": { 2072 | "version": "0.30.12", 2073 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", 2074 | "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", 2075 | "dependencies": { 2076 | "@jridgewell/sourcemap-codec": "^1.5.0" 2077 | } 2078 | }, 2079 | "node_modules/merge-stream": { 2080 | "version": "2.0.0", 2081 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 2082 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 2083 | "dev": true 2084 | }, 2085 | "node_modules/mimic-fn": { 2086 | "version": "4.0.0", 2087 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", 2088 | "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", 2089 | "dev": true, 2090 | "engines": { 2091 | "node": ">=12" 2092 | }, 2093 | "funding": { 2094 | "url": "https://github.com/sponsors/sindresorhus" 2095 | } 2096 | }, 2097 | "node_modules/mitt": { 2098 | "version": "3.0.1", 2099 | "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", 2100 | "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", 2101 | "dev": true 2102 | }, 2103 | "node_modules/mrmime": { 2104 | "version": "2.0.0", 2105 | "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", 2106 | "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", 2107 | "dev": true, 2108 | "engines": { 2109 | "node": ">=10" 2110 | } 2111 | }, 2112 | "node_modules/ms": { 2113 | "version": "2.1.3", 2114 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2115 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 2116 | "dev": true 2117 | }, 2118 | "node_modules/nanoid": { 2119 | "version": "3.3.7", 2120 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", 2121 | "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", 2122 | "funding": [ 2123 | { 2124 | "type": "github", 2125 | "url": "https://github.com/sponsors/ai" 2126 | } 2127 | ], 2128 | "bin": { 2129 | "nanoid": "bin/nanoid.cjs" 2130 | }, 2131 | "engines": { 2132 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 2133 | } 2134 | }, 2135 | "node_modules/node-releases": { 2136 | "version": "2.0.18", 2137 | "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", 2138 | "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", 2139 | "dev": true 2140 | }, 2141 | "node_modules/npm-run-path": { 2142 | "version": "5.3.0", 2143 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", 2144 | "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", 2145 | "dev": true, 2146 | "dependencies": { 2147 | "path-key": "^4.0.0" 2148 | }, 2149 | "engines": { 2150 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 2151 | }, 2152 | "funding": { 2153 | "url": "https://github.com/sponsors/sindresorhus" 2154 | } 2155 | }, 2156 | "node_modules/npm-run-path/node_modules/path-key": { 2157 | "version": "4.0.0", 2158 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", 2159 | "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", 2160 | "dev": true, 2161 | "engines": { 2162 | "node": ">=12" 2163 | }, 2164 | "funding": { 2165 | "url": "https://github.com/sponsors/sindresorhus" 2166 | } 2167 | }, 2168 | "node_modules/onetime": { 2169 | "version": "6.0.0", 2170 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", 2171 | "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", 2172 | "dev": true, 2173 | "dependencies": { 2174 | "mimic-fn": "^4.0.0" 2175 | }, 2176 | "engines": { 2177 | "node": ">=12" 2178 | }, 2179 | "funding": { 2180 | "url": "https://github.com/sponsors/sindresorhus" 2181 | } 2182 | }, 2183 | "node_modules/open": { 2184 | "version": "10.1.0", 2185 | "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", 2186 | "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", 2187 | "dev": true, 2188 | "dependencies": { 2189 | "default-browser": "^5.2.1", 2190 | "define-lazy-prop": "^3.0.0", 2191 | "is-inside-container": "^1.0.0", 2192 | "is-wsl": "^3.1.0" 2193 | }, 2194 | "engines": { 2195 | "node": ">=18" 2196 | }, 2197 | "funding": { 2198 | "url": "https://github.com/sponsors/sindresorhus" 2199 | } 2200 | }, 2201 | "node_modules/path-key": { 2202 | "version": "3.1.1", 2203 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 2204 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 2205 | "dev": true, 2206 | "engines": { 2207 | "node": ">=8" 2208 | } 2209 | }, 2210 | "node_modules/pathe": { 2211 | "version": "1.1.2", 2212 | "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", 2213 | "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", 2214 | "dev": true 2215 | }, 2216 | "node_modules/perfect-debounce": { 2217 | "version": "1.0.0", 2218 | "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", 2219 | "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", 2220 | "dev": true 2221 | }, 2222 | "node_modules/picocolors": { 2223 | "version": "1.1.1", 2224 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 2225 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" 2226 | }, 2227 | "node_modules/picomatch": { 2228 | "version": "4.0.2", 2229 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 2230 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 2231 | "dev": true, 2232 | "engines": { 2233 | "node": ">=12" 2234 | }, 2235 | "funding": { 2236 | "url": "https://github.com/sponsors/jonschlinkert" 2237 | } 2238 | }, 2239 | "node_modules/postcss": { 2240 | "version": "8.4.47", 2241 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", 2242 | "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", 2243 | "funding": [ 2244 | { 2245 | "type": "opencollective", 2246 | "url": "https://opencollective.com/postcss/" 2247 | }, 2248 | { 2249 | "type": "tidelift", 2250 | "url": "https://tidelift.com/funding/github/npm/postcss" 2251 | }, 2252 | { 2253 | "type": "github", 2254 | "url": "https://github.com/sponsors/ai" 2255 | } 2256 | ], 2257 | "dependencies": { 2258 | "nanoid": "^3.3.7", 2259 | "picocolors": "^1.1.0", 2260 | "source-map-js": "^1.2.1" 2261 | }, 2262 | "engines": { 2263 | "node": "^10 || ^12 || >=14" 2264 | } 2265 | }, 2266 | "node_modules/require-directory": { 2267 | "version": "2.1.1", 2268 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 2269 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 2270 | "dev": true, 2271 | "engines": { 2272 | "node": ">=0.10.0" 2273 | } 2274 | }, 2275 | "node_modules/rfdc": { 2276 | "version": "1.4.1", 2277 | "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", 2278 | "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", 2279 | "dev": true 2280 | }, 2281 | "node_modules/rollup": { 2282 | "version": "4.24.0", 2283 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz", 2284 | "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==", 2285 | "dev": true, 2286 | "dependencies": { 2287 | "@types/estree": "1.0.6" 2288 | }, 2289 | "bin": { 2290 | "rollup": "dist/bin/rollup" 2291 | }, 2292 | "engines": { 2293 | "node": ">=18.0.0", 2294 | "npm": ">=8.0.0" 2295 | }, 2296 | "optionalDependencies": { 2297 | "@rollup/rollup-android-arm-eabi": "4.24.0", 2298 | "@rollup/rollup-android-arm64": "4.24.0", 2299 | "@rollup/rollup-darwin-arm64": "4.24.0", 2300 | "@rollup/rollup-darwin-x64": "4.24.0", 2301 | "@rollup/rollup-linux-arm-gnueabihf": "4.24.0", 2302 | "@rollup/rollup-linux-arm-musleabihf": "4.24.0", 2303 | "@rollup/rollup-linux-arm64-gnu": "4.24.0", 2304 | "@rollup/rollup-linux-arm64-musl": "4.24.0", 2305 | "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0", 2306 | "@rollup/rollup-linux-riscv64-gnu": "4.24.0", 2307 | "@rollup/rollup-linux-s390x-gnu": "4.24.0", 2308 | "@rollup/rollup-linux-x64-gnu": "4.24.0", 2309 | "@rollup/rollup-linux-x64-musl": "4.24.0", 2310 | "@rollup/rollup-win32-arm64-msvc": "4.24.0", 2311 | "@rollup/rollup-win32-ia32-msvc": "4.24.0", 2312 | "@rollup/rollup-win32-x64-msvc": "4.24.0", 2313 | "fsevents": "~2.3.2" 2314 | } 2315 | }, 2316 | "node_modules/run-applescript": { 2317 | "version": "7.0.0", 2318 | "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", 2319 | "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", 2320 | "dev": true, 2321 | "engines": { 2322 | "node": ">=18" 2323 | }, 2324 | "funding": { 2325 | "url": "https://github.com/sponsors/sindresorhus" 2326 | } 2327 | }, 2328 | "node_modules/rxjs": { 2329 | "version": "7.8.1", 2330 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", 2331 | "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", 2332 | "dev": true, 2333 | "dependencies": { 2334 | "tslib": "^2.1.0" 2335 | } 2336 | }, 2337 | "node_modules/semver": { 2338 | "version": "6.3.1", 2339 | "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 2340 | "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 2341 | "dev": true, 2342 | "bin": { 2343 | "semver": "bin/semver.js" 2344 | } 2345 | }, 2346 | "node_modules/shebang-command": { 2347 | "version": "2.0.0", 2348 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 2349 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 2350 | "dev": true, 2351 | "dependencies": { 2352 | "shebang-regex": "^3.0.0" 2353 | }, 2354 | "engines": { 2355 | "node": ">=8" 2356 | } 2357 | }, 2358 | "node_modules/shebang-regex": { 2359 | "version": "3.0.0", 2360 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 2361 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 2362 | "dev": true, 2363 | "engines": { 2364 | "node": ">=8" 2365 | } 2366 | }, 2367 | "node_modules/shell-quote": { 2368 | "version": "1.8.1", 2369 | "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", 2370 | "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", 2371 | "dev": true, 2372 | "funding": { 2373 | "url": "https://github.com/sponsors/ljharb" 2374 | } 2375 | }, 2376 | "node_modules/signal-exit": { 2377 | "version": "4.1.0", 2378 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 2379 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 2380 | "dev": true, 2381 | "engines": { 2382 | "node": ">=14" 2383 | }, 2384 | "funding": { 2385 | "url": "https://github.com/sponsors/isaacs" 2386 | } 2387 | }, 2388 | "node_modules/sirv": { 2389 | "version": "3.0.0", 2390 | "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz", 2391 | "integrity": "sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==", 2392 | "dev": true, 2393 | "dependencies": { 2394 | "@polka/url": "^1.0.0-next.24", 2395 | "mrmime": "^2.0.0", 2396 | "totalist": "^3.0.0" 2397 | }, 2398 | "engines": { 2399 | "node": ">=18" 2400 | } 2401 | }, 2402 | "node_modules/source-map-js": { 2403 | "version": "1.2.1", 2404 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 2405 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 2406 | "engines": { 2407 | "node": ">=0.10.0" 2408 | } 2409 | }, 2410 | "node_modules/speakingurl": { 2411 | "version": "14.0.1", 2412 | "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", 2413 | "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", 2414 | "dev": true, 2415 | "engines": { 2416 | "node": ">=0.10.0" 2417 | } 2418 | }, 2419 | "node_modules/string-width": { 2420 | "version": "4.2.3", 2421 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 2422 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 2423 | "dev": true, 2424 | "dependencies": { 2425 | "emoji-regex": "^8.0.0", 2426 | "is-fullwidth-code-point": "^3.0.0", 2427 | "strip-ansi": "^6.0.1" 2428 | }, 2429 | "engines": { 2430 | "node": ">=8" 2431 | } 2432 | }, 2433 | "node_modules/strip-ansi": { 2434 | "version": "6.0.1", 2435 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 2436 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 2437 | "dev": true, 2438 | "dependencies": { 2439 | "ansi-regex": "^5.0.1" 2440 | }, 2441 | "engines": { 2442 | "node": ">=8" 2443 | } 2444 | }, 2445 | "node_modules/strip-final-newline": { 2446 | "version": "3.0.0", 2447 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", 2448 | "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", 2449 | "dev": true, 2450 | "engines": { 2451 | "node": ">=12" 2452 | }, 2453 | "funding": { 2454 | "url": "https://github.com/sponsors/sindresorhus" 2455 | } 2456 | }, 2457 | "node_modules/superjson": { 2458 | "version": "2.2.1", 2459 | "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", 2460 | "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", 2461 | "dev": true, 2462 | "dependencies": { 2463 | "copy-anything": "^3.0.2" 2464 | }, 2465 | "engines": { 2466 | "node": ">=16" 2467 | } 2468 | }, 2469 | "node_modules/supports-color": { 2470 | "version": "8.1.1", 2471 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 2472 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 2473 | "dev": true, 2474 | "dependencies": { 2475 | "has-flag": "^4.0.0" 2476 | }, 2477 | "engines": { 2478 | "node": ">=10" 2479 | }, 2480 | "funding": { 2481 | "url": "https://github.com/chalk/supports-color?sponsor=1" 2482 | } 2483 | }, 2484 | "node_modules/svg-tags": { 2485 | "version": "1.0.0", 2486 | "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", 2487 | "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", 2488 | "dev": true 2489 | }, 2490 | "node_modules/totalist": { 2491 | "version": "3.0.1", 2492 | "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", 2493 | "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", 2494 | "dev": true, 2495 | "engines": { 2496 | "node": ">=6" 2497 | } 2498 | }, 2499 | "node_modules/tree-kill": { 2500 | "version": "1.2.2", 2501 | "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", 2502 | "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", 2503 | "dev": true, 2504 | "bin": { 2505 | "tree-kill": "cli.js" 2506 | } 2507 | }, 2508 | "node_modules/tslib": { 2509 | "version": "2.8.0", 2510 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz", 2511 | "integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==", 2512 | "dev": true 2513 | }, 2514 | "node_modules/universalify": { 2515 | "version": "2.0.1", 2516 | "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", 2517 | "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", 2518 | "dev": true, 2519 | "engines": { 2520 | "node": ">= 10.0.0" 2521 | } 2522 | }, 2523 | "node_modules/update-browserslist-db": { 2524 | "version": "1.1.1", 2525 | "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", 2526 | "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", 2527 | "dev": true, 2528 | "funding": [ 2529 | { 2530 | "type": "opencollective", 2531 | "url": "https://opencollective.com/browserslist" 2532 | }, 2533 | { 2534 | "type": "tidelift", 2535 | "url": "https://tidelift.com/funding/github/npm/browserslist" 2536 | }, 2537 | { 2538 | "type": "github", 2539 | "url": "https://github.com/sponsors/ai" 2540 | } 2541 | ], 2542 | "dependencies": { 2543 | "escalade": "^3.2.0", 2544 | "picocolors": "^1.1.0" 2545 | }, 2546 | "bin": { 2547 | "update-browserslist-db": "cli.js" 2548 | }, 2549 | "peerDependencies": { 2550 | "browserslist": ">= 4.21.0" 2551 | } 2552 | }, 2553 | "node_modules/vite": { 2554 | "version": "5.4.10", 2555 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz", 2556 | "integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==", 2557 | "dev": true, 2558 | "dependencies": { 2559 | "esbuild": "^0.21.3", 2560 | "postcss": "^8.4.43", 2561 | "rollup": "^4.20.0" 2562 | }, 2563 | "bin": { 2564 | "vite": "bin/vite.js" 2565 | }, 2566 | "engines": { 2567 | "node": "^18.0.0 || >=20.0.0" 2568 | }, 2569 | "funding": { 2570 | "url": "https://github.com/vitejs/vite?sponsor=1" 2571 | }, 2572 | "optionalDependencies": { 2573 | "fsevents": "~2.3.3" 2574 | }, 2575 | "peerDependencies": { 2576 | "@types/node": "^18.0.0 || >=20.0.0", 2577 | "less": "*", 2578 | "lightningcss": "^1.21.0", 2579 | "sass": "*", 2580 | "sass-embedded": "*", 2581 | "stylus": "*", 2582 | "sugarss": "*", 2583 | "terser": "^5.4.0" 2584 | }, 2585 | "peerDependenciesMeta": { 2586 | "@types/node": { 2587 | "optional": true 2588 | }, 2589 | "less": { 2590 | "optional": true 2591 | }, 2592 | "lightningcss": { 2593 | "optional": true 2594 | }, 2595 | "sass": { 2596 | "optional": true 2597 | }, 2598 | "sass-embedded": { 2599 | "optional": true 2600 | }, 2601 | "stylus": { 2602 | "optional": true 2603 | }, 2604 | "sugarss": { 2605 | "optional": true 2606 | }, 2607 | "terser": { 2608 | "optional": true 2609 | } 2610 | } 2611 | }, 2612 | "node_modules/vite-hot-client": { 2613 | "version": "0.2.3", 2614 | "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.3.tgz", 2615 | "integrity": "sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg==", 2616 | "dev": true, 2617 | "funding": { 2618 | "url": "https://github.com/sponsors/antfu" 2619 | }, 2620 | "peerDependencies": { 2621 | "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0" 2622 | } 2623 | }, 2624 | "node_modules/vite-plugin-inspect": { 2625 | "version": "0.8.7", 2626 | "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.7.tgz", 2627 | "integrity": "sha512-/XXou3MVc13A5O9/2Nd6xczjrUwt7ZyI9h8pTnUMkr5SshLcb0PJUOVq2V+XVkdeU4njsqAtmK87THZuO2coGA==", 2628 | "dev": true, 2629 | "dependencies": { 2630 | "@antfu/utils": "^0.7.10", 2631 | "@rollup/pluginutils": "^5.1.0", 2632 | "debug": "^4.3.6", 2633 | "error-stack-parser-es": "^0.1.5", 2634 | "fs-extra": "^11.2.0", 2635 | "open": "^10.1.0", 2636 | "perfect-debounce": "^1.0.0", 2637 | "picocolors": "^1.0.1", 2638 | "sirv": "^2.0.4" 2639 | }, 2640 | "engines": { 2641 | "node": ">=14" 2642 | }, 2643 | "funding": { 2644 | "url": "https://github.com/sponsors/antfu" 2645 | }, 2646 | "peerDependencies": { 2647 | "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0" 2648 | }, 2649 | "peerDependenciesMeta": { 2650 | "@nuxt/kit": { 2651 | "optional": true 2652 | } 2653 | } 2654 | }, 2655 | "node_modules/vite-plugin-inspect/node_modules/sirv": { 2656 | "version": "2.0.4", 2657 | "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", 2658 | "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", 2659 | "dev": true, 2660 | "dependencies": { 2661 | "@polka/url": "^1.0.0-next.24", 2662 | "mrmime": "^2.0.0", 2663 | "totalist": "^3.0.0" 2664 | }, 2665 | "engines": { 2666 | "node": ">= 10" 2667 | } 2668 | }, 2669 | "node_modules/vite-plugin-vue-devtools": { 2670 | "version": "7.5.3", 2671 | "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.5.3.tgz", 2672 | "integrity": "sha512-gpR4S1anwc3rEapcealw0EATfQHO7jLCPqqT2qZAYLyVXsRi+Ysk7Z+kr/iq0sROfcVfSNAqAMB7foZobj2m5Q==", 2673 | "dev": true, 2674 | "dependencies": { 2675 | "@vue/devtools-core": "^7.5.3", 2676 | "@vue/devtools-kit": "^7.5.3", 2677 | "@vue/devtools-shared": "^7.5.3", 2678 | "execa": "^8.0.1", 2679 | "sirv": "^3.0.0", 2680 | "vite-plugin-inspect": "^0.8.7", 2681 | "vite-plugin-vue-inspector": "^5.2.0" 2682 | }, 2683 | "engines": { 2684 | "node": ">=v14.21.3" 2685 | }, 2686 | "peerDependencies": { 2687 | "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0" 2688 | } 2689 | }, 2690 | "node_modules/vite-plugin-vue-inspector": { 2691 | "version": "5.2.0", 2692 | "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.2.0.tgz", 2693 | "integrity": "sha512-wWxyb9XAtaIvV/Lr7cqB1HIzmHZFVUJsTNm3yAxkS87dgh/Ky4qr2wDEWNxF23fdhVa3jQ8MZREpr4XyiuaRqA==", 2694 | "dev": true, 2695 | "dependencies": { 2696 | "@babel/core": "^7.23.0", 2697 | "@babel/plugin-proposal-decorators": "^7.23.0", 2698 | "@babel/plugin-syntax-import-attributes": "^7.22.5", 2699 | "@babel/plugin-syntax-import-meta": "^7.10.4", 2700 | "@babel/plugin-transform-typescript": "^7.22.15", 2701 | "@vue/babel-plugin-jsx": "^1.1.5", 2702 | "@vue/compiler-dom": "^3.3.4", 2703 | "kolorist": "^1.8.0", 2704 | "magic-string": "^0.30.4" 2705 | }, 2706 | "peerDependencies": { 2707 | "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0" 2708 | } 2709 | }, 2710 | "node_modules/vue": { 2711 | "version": "3.5.12", 2712 | "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", 2713 | "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", 2714 | "dependencies": { 2715 | "@vue/compiler-dom": "3.5.12", 2716 | "@vue/compiler-sfc": "3.5.12", 2717 | "@vue/runtime-dom": "3.5.12", 2718 | "@vue/server-renderer": "3.5.12", 2719 | "@vue/shared": "3.5.12" 2720 | }, 2721 | "peerDependencies": { 2722 | "typescript": "*" 2723 | }, 2724 | "peerDependenciesMeta": { 2725 | "typescript": { 2726 | "optional": true 2727 | } 2728 | } 2729 | }, 2730 | "node_modules/which": { 2731 | "version": "2.0.2", 2732 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2733 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2734 | "dev": true, 2735 | "dependencies": { 2736 | "isexe": "^2.0.0" 2737 | }, 2738 | "bin": { 2739 | "node-which": "bin/node-which" 2740 | }, 2741 | "engines": { 2742 | "node": ">= 8" 2743 | } 2744 | }, 2745 | "node_modules/wrap-ansi": { 2746 | "version": "7.0.0", 2747 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 2748 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 2749 | "dev": true, 2750 | "dependencies": { 2751 | "ansi-styles": "^4.0.0", 2752 | "string-width": "^4.1.0", 2753 | "strip-ansi": "^6.0.0" 2754 | }, 2755 | "engines": { 2756 | "node": ">=10" 2757 | }, 2758 | "funding": { 2759 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 2760 | } 2761 | }, 2762 | "node_modules/y18n": { 2763 | "version": "5.0.8", 2764 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 2765 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 2766 | "dev": true, 2767 | "engines": { 2768 | "node": ">=10" 2769 | } 2770 | }, 2771 | "node_modules/yallist": { 2772 | "version": "3.1.1", 2773 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", 2774 | "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", 2775 | "dev": true 2776 | }, 2777 | "node_modules/yargs": { 2778 | "version": "17.7.2", 2779 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", 2780 | "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", 2781 | "dev": true, 2782 | "dependencies": { 2783 | "cliui": "^8.0.1", 2784 | "escalade": "^3.1.1", 2785 | "get-caller-file": "^2.0.5", 2786 | "require-directory": "^2.1.1", 2787 | "string-width": "^4.2.3", 2788 | "y18n": "^5.0.5", 2789 | "yargs-parser": "^21.1.1" 2790 | }, 2791 | "engines": { 2792 | "node": ">=12" 2793 | } 2794 | }, 2795 | "node_modules/yargs-parser": { 2796 | "version": "21.1.1", 2797 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", 2798 | "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", 2799 | "dev": true, 2800 | "engines": { 2801 | "node": ">=12" 2802 | } 2803 | } 2804 | } 2805 | } 2806 | --------------------------------------------------------------------------------