diff --git a/.agent/skills/frontend_design/lib-vue-components.md b/.agent/skills/frontend_design/lib-vue-components.md index b0f75de..78697c6 100644 --- a/.agent/skills/frontend_design/lib-vue-components.md +++ b/.agent/skills/frontend_design/lib-vue-components.md @@ -1,40 +1,86 @@ -# Lib Vue Components Documentation +# PilotUI - LLM Documentation -Generated on: 2025-11-16T14:05:12.514Z +> Generated on: 3/4/2026, 4:13:37 PM + +## 🤖 LLM Instructions +This document is optimized for Large Language Models. It contains the complete documentation for **PilotUI**, a Vue 3 component library. + +### How to use this doc: +- **Component Search**: Use the Table of Contents below to find specific components. +- **Code Examples**: All component examples are provided in fenced code blocks with language identifiers. +- **Prop Tables**: Component properties, events, and slots are documented in Markdown tables. +- **Implementation**: When asked to implement a UI, refer to the available components and their usage patterns described here. + +--- + +## 📋 Table of Contents + +### Getting Started +- [Installation](#getting-started-installation) + +### Getting Started How To +- [Use](#getting-started-how-to-use) + +### Getting Started Global +- [Configuration](#getting-started-global-configuration) + +### Shell +- [Approot](#shell-approot) +- [Dashboardshell](#shell-dashboardshell) +- [Horizontalmenu](#shell-horizontalmenu) +- [Sidebarmenu](#shell-sidebarmenu) + +### Icons Alternative Icon +- [Packs](#icons-alternative-icon-packs) + +### Icons Icon +- [Gallery](#icons-icon-gallery) + +### Utilities +- [Toast](#utilities-toast) + +### Complex +- [Modal](#complex-modal) +- [Pagination](#complex-pagination) + +### Elements +- [Avatar](#elements-avatar) +- [Avatargroup](#elements-avatargroup) +- [Button](#elements-button) +- [Card](#elements-card) +- [Dropdown](#elements-dropdown) +- [Iconbutton](#elements-iconbutton) +- [Progress](#elements-progress) +- [Tabs](#elements-tabs) +- [Tooltip](#elements-tooltip) + +### Form +- [Checkboxinput](#form-checkboxinput) +- [Fileinputbutton](#form-fileinputbutton) +- [Fileinputcombo](#form-fileinputcombo) +- [Input](#form-input) +- [Inputgroup](#form-inputgroup) +- [Select](#form-select) +- [Switchball](#form-switchball) +- [Textarea](#form-textarea) --- + ## Getting Started / Installation +**Source URL**: http://localhost:6006/?path=/docs/getting-started-installation--docs + ### Installation Guide #### Prerequisites - A working Vue 3 or Nuxt 3 project -- GitHub account with package access - Node.js and npm/yarn installed #### Setup Steps -##### 1\. GitHub Authentication - -1. Create a GitHub personal access token: - - - Go to GitHub Settings → Developer Settings → Personal Access Tokens - - Generate a new token with `read:packages` permission - - Copy the generated token - - For detailed instructions, watch this guide -2. Create an `.npmrc` file in your project root: - - ``` - @codebridger:registry=https://npm.pkg.github.com - //npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN - ``` - - Replace `YOUR_GITHUB_TOKEN` with the token you created. - - -##### 2\. Package Installation +##### 1\. Package Installation Install the package using npm or yarn: @@ -138,8 +184,11 @@ After installation, you can start using the components in your application. Chec --- + ## Getting Started How To / Use +**Source URL**: http://localhost:6006/?path=/docs/getting-started-how-to-use--docs + ### Component library We are two indie developers who love to build things. We are building a component library for vue and nuxt projects. it inspired by `vristo` and powered by `tailwindcss`. @@ -204,8 +253,11 @@ appStore.setTheme("dark"); --- + ## Getting Started Global / Configuration +**Source URL**: http://localhost:6006/?path=/docs/getting-started-global-configuration--docs + #### Using `useAppStore` Import and use the `useAppStore` in your components to access and modify the global state. @@ -323,8 +375,11 @@ function toggleSidebar() { --- + ## Shell / Approot +**Source URL**: http://localhost:6006/?path=/docs/shell-approot--docs + ### AppRoot Top-level shell that wires global layout concerns: color scheme, direction (LTR/RTL), and layout style. Wrap your application to ensure consistent theming and structure. @@ -355,8 +410,11 @@ Use as the root wrapper for app pages/stories. Combine with DashboardShell for f --- + ## Shell / Dashboardshell +**Source URL**: http://localhost:6006/?path=/docs/shell-dashboardshell--docs + ### DashboardShell Composable page shell providing header, horizontal menu, sidebar, content, and footer slots. Supports vertical and horizontal navigation styles. @@ -425,8 +483,11 @@ Wrap application pages to provide consistent navigation and scaffolding. Fill sl --- + ## Shell / Horizontalmenu +**Source URL**: http://localhost:6006/?path=/docs/shell-horizontalmenu--docs + ### HorizontalMenu Responsive top navigation bar rendering a hierarchy of items with icons and labels. Integrates with the shell store to switch layout style. @@ -974,8 +1035,11 @@ const items = [ --- + ## Shell / Sidebarmenu +**Source URL**: http://localhost:6006/?path=/docs/shell-sidebarmenu--docs + ### SidebarMenu ### SidebarMenu @@ -1579,8 +1643,11 @@ const items = [ --- + ## Icons Alternative Icon / Packs +**Source URL**: http://localhost:6006/?path=/docs/icons-alternative-icon-packs--docs + ### Alternative Icon Packs Using Iconify as an Alternative Icon Pack with Tailwind CSS @@ -1641,8 +1708,11 @@ For more details on using Iconify with Tailwind (and other setup options like ad --- + ## Icons Icon / Gallery +**Source URL**: http://localhost:6006/?path=/docs/icons-icon-gallery--docs + ### Icon Gallery To use icons listed in this page you need to import the `icon` component and provide a name from the list below. @@ -1999,8 +2069,11 @@ IconStar --- + ## Utilities / Toast +**Source URL**: http://localhost:6006/?path=/docs/utilities-toast--docs + ### Toast Utility Functions The `toast.ts` file provides utility functions for displaying toast notifications using SweetAlert2. These functions allow you to show different types of toast messages with various configurations. @@ -2057,8 +2130,11 @@ toastInfo('This is an info toast message'); --- + ## Complex / Modal +**Source URL**: http://localhost:6006/?path=/docs/complex-modal--docs + ### Modal A flexible dialog for confirmations, forms, and rich content. Provides slots for trigger, title, default content, and footer; supports sizes, vertical alignment, and animations. @@ -2100,8 +2176,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin This is a basic modal with default settings. You can customize the content, size, and behavior.

- - + +
@@ -2158,8 +2234,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin This is a basic modal with default settings. You can customize the content, size, and behavior.

- - + +
@@ -2186,7 +2262,7 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin template: ` @@ -2197,7 +2273,7 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin

This modal uses a custom trigger button instead of the default one.

- +
@@ -2231,8 +2307,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin This modal has a custom title in the header area.

- - + +
@@ -2275,8 +2351,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin This modal demonstrates the title slot functionality. You can add custom content, icons, and styling to the title area.

- - + +
@@ -2313,8 +2389,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin You must use the action buttons to close it.

- - + +
@@ -2370,8 +2446,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin
- - + +
@@ -2408,7 +2484,7 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin

This modal uses a zoom-in animation effect. Try opening it again to see the animation.

- + @@ -2457,10 +2533,10 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin @@ -2496,8 +2572,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin This modal has the close button hidden. You must use the action buttons to close it.

- - + +
@@ -2535,8 +2611,8 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin Are you sure you want to proceed?

- - + +
@@ -2552,8 +2628,11 @@ Use for tasks that require focused attention. Keep content concise; avoid nestin --- + ## Complex / Pagination +**Source URL**: http://localhost:6006/?path=/docs/complex-pagination--docs + ### Pagination ``` @@ -2645,8 +2724,11 @@ Current Page: 1 --- + ## Elements / Avatar +**Source URL**: http://localhost:6006/?path=/docs/elements-avatar--docs + ### Avatar Displays a user image or placeholder with configurable size and rounding. Optional presence indicator conveys online/offline state. @@ -2843,8 +2925,11 @@ Use in lists, headers, and cards. Combine with AvatarGroup to show multiple part --- + ## Elements / Avatargroup +**Source URL**: http://localhost:6006/?path=/docs/elements-avatargroup--docs + ### AvatarGroup #### A container component for grouping multiple avatars @@ -3195,8 +3280,11 @@ Avatar group with RTL (Right-to-Left) layout support enabled. --- + ## Elements / Button +**Source URL**: http://localhost:6006/?path=/docs/elements-button--docs + ### Button A flexible, accessible button with rich visual variants and behaviors. Use it for primary and secondary actions, icon-only actions, links, and async/loading flows. @@ -3561,8 +3649,11 @@ Chip clicks: 0 --- + ## Elements / Card +**Source URL**: http://localhost:6006/?path=/docs/elements-card--docs + ### Card A versatile Card component that serves as a container for content with consistent styling. The component features: @@ -3997,8 +4088,11 @@ Option 1Option 2 --- + ## Elements / Dropdown +**Source URL**: http://localhost:6006/?path=/docs/elements-dropdown--docs + ### Dropdown Contextual menu/popover for secondary actions. Provides trigger slot and body slot, positioning via Popper, and rich interaction modes. @@ -4631,8 +4725,11 @@ Use for menus, quick filters, and small forms. Keep actions concise and avoid de --- + ## Elements / Iconbutton +**Source URL**: http://localhost:6006/?path=/docs/elements-iconbutton--docs + ### IconButton A compact, versatile button optimized for icons or avatars. Works as a clickable control by default and as a decorative badge when badge is true. @@ -4990,8 +5087,11 @@ Badge mode with labels in different colors - perfect for status indicators and t --- + ## Elements / Progress +**Source URL**: http://localhost:6006/?path=/docs/elements-progress--docs + ### Progress A versatile progress bar component with a clean, modern design. @@ -5286,8 +5386,11 @@ The component includes several interactive features: --- + ## Elements / Tabs +**Source URL**: http://localhost:6006/?path=/docs/elements-tabs--docs + ### Tabs ### Tabs Component @@ -5959,8 +6062,11 @@ This content won't be accessible because the tab is disabled. --- + ## Elements / Tooltip +**Source URL**: http://localhost:6006/?path=/docs/elements-tooltip--docs + ### Tooltip Wrap any element to show helpful text on hover or focus. Placement, delay, and color are configurable for consistent guidance. @@ -6057,8 +6163,11 @@ This is a much longer tooltip message that demonstrates how the tooltip handles --- + ## Form / Checkboxinput +**Source URL**: http://localhost:6006/?path=/docs/form-checkboxinput--docs + ### CheckboxInput A flexible single checkbox component that supports various visual variants, color schemes, and states. The component features: @@ -6699,8 +6808,11 @@ Selected: None --- + ## Form / Fileinputbutton +**Source URL**: http://localhost:6006/?path=/docs/form-fileinputbutton--docs + ### FileInputButton Accessible file picker that pairs a styled button with a native file input under the hood. Supports accept filters, capture hints, and multiple selection. @@ -6747,7 +6859,7 @@ Upload File | errorMessage | Error message to displaystring | "" | | | id | string | "" | | | capture | Capture method for file input (e.g., "user" or "environment")"user""environment"boolean | - | | -| size | Size attribute for the file inputnumber | 0 | | +| size | Size attribute for the file inputnumber | - | | | events | | | blur | Emitted when the input loses focusFocusEvent | - | - | | focus | Emitted when the input gains focusFocusEvent | - | - | @@ -6849,8 +6961,11 @@ Take Photo --- + ## Form / Fileinputcombo +**Source URL**: http://localhost:6006/?path=/docs/form-fileinputcombo--docs + ### FileInputCombo A versatile file upload component that supports both drag-and-drop and click-to-upload functionality. The component provides a modern interface with file previews, upload progress tracking, and comprehensive file management features. @@ -7441,8 +7556,11 @@ Demonstrates error toast notifications --- + ## Form / Input +**Source URL**: http://localhost:6006/?path=/docs/form-input--docs + ### Input ### Input Component @@ -7858,8 +7976,11 @@ Icon Positioning Demo --- + ## Form / Inputgroup +**Source URL**: http://localhost:6006/?path=/docs/form-inputgroup--docs + ### InputGroup ### InputGroup @@ -8128,8 +8249,11 @@ Username --- + ## Form / Select +**Source URL**: http://localhost:6006/?path=/docs/form-select--docs + ### Select ### Select Component @@ -9227,8 +9351,11 @@ const options = ["Red","Green","Blue","Yellow","Purple","Orange"]; --- + ## Form / Switchball +**Source URL**: http://localhost:6006/?path=/docs/form-switchball--docs + ### SwitchBall ### SwitchBall Component @@ -9433,8 +9560,11 @@ Custom Icon Switch --- + ## Form / Textarea +**Source URL**: http://localhost:6006/?path=/docs/form-textarea--docs + ### TextArea ### TextArea Component @@ -9765,4 +9895,3 @@ Icon Positioning Demo ``` --- - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 20865a9..5fd8ff6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -37,12 +37,80 @@ jobs: run: | git push origin main --follow-tags + - name: Generate AI Release Notes + id: generate-notes + env: + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY_FOR_PR_DESC_GENERATOR }} + MODEL_NAME: "gpt-5.4-nano-2026-03-17" + run: | + # Get the new tag from the previous step + NEW_TAG=${{ steps.version-bump.outputs.version }} + + # Find the previous tag (excluding the one we just created) + PREV_TAG=$(git describe --tags --abbrev=0 $NEW_TAG^ 2>/dev/null || echo "") + + echo "Current Tag: $NEW_TAG" + echo "Previous Tag: $PREV_TAG" + + if [ -z "$PREV_TAG" ]; then + # If no previous tag, take all commits + COMMITS=$(git log --oneline --no-merges) + else + # Get commits between the two tags, excluding the version bump commit itself + COMMITS=$(git log $PREV_TAG..$NEW_TAG^ --oneline --no-merges) + fi + + if [ -z "$COMMITS" ]; then + COMMITS="No changes detected." + fi + + echo "=== Commits to summarize ===" + echo "$COMMITS" + echo "============================" + + # Prepare JSON payload for OpenAI API + JSON_PAYLOAD=$(jq -n --arg commits "$COMMITS" --arg model "$MODEL_NAME" '{ + model: $model, + messages: [ + { + role: "system", + content: "You are a professional release manager. Summarize commit messages into clear, user-friendly release notes with markdown sections for Features, Bug Fixes, and Improvements." + }, + { + role: "user", + content: ("Summarize these commits into professional release notes. Use bullet points and be concise.\n\nCommits:\n" + $commits) + } + ], + temperature: 0.7 + }') + + # Call OpenAI API + RESPONSE=$(curl -s -X POST "https://api.openai.com/v1/chat/completions" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer ${OPENAI_API_KEY}" \ + -d "$JSON_PAYLOAD") + + # Extract notes from response + NOTES=$(echo "$RESPONSE" | jq -r '.choices[0].message.content' 2>/dev/null || echo "") + + # Fallback if AI fails or returns null + if [ "$NOTES" == "null" ] || [ -z "$NOTES" ]; then + echo "Warning: AI generation failed or returned empty. Falling back to commit list." + echo "API Response: $RESPONSE" + NOTES="### Changes\n\n$COMMITS" + fi + + # Output the notes for the next step + echo "changelog<> $GITHUB_OUTPUT + echo "$NOTES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - name: Create Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.version-bump.outputs.version }} name: Release ${{ steps.version-bump.outputs.version }} - generate_release_notes: true + body: ${{ steps.generate-notes.outputs.changelog }} draft: false prerelease: false env: diff --git a/README.md b/README.md index 11f3a22..d9df923 100644 --- a/README.md +++ b/README.md @@ -1,85 +1,91 @@ + # 🌊 FrameFlow -# 🎬 VGTU Video Summarization -![Project Process](./docs/imgs/process.jpeg) - +FrameFlow Banner --- -A high-fidelity platform that transforms long-form video content into concise, meaningful highlights. By leveraging **Google Gemini's** multimodal intelligence and precise **FFmpeg** engineering, it provides a seamless chat-based refinement experience. + **FrameFlow** is a high-fidelity multimedia platform that bridges the gap between raw video/image assets and creative intelligence. By fusing **Google Gemini's** multimodal brain with precise **FFmpeg** engineering, FrameFlow transforms how you consume, extract, and generate media. + +
+ + [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + [![Platform: Electron](https://img.shields.io/badge/Platform-Electron-lightgrey.svg)](https://www.electronjs.org/) + [![Framework: Vue 3](https://img.shields.io/badge/Framework-Vue%203-4fc08d.svg)](https://vuejs.org/) + [![AI: Google Gemini](https://img.shields.io/badge/AI-Google%20Gemini-4285F4.svg)](https://deepmind.google/technologies/gemini/) -**Key Use Cases:** -- 🎓 **Academic Hub**: Condense 2-hour technical lectures into 5-minute study guides. -- 📱 **Content Creation**: Generate social media teasers from raw footage with natural language. -- 🔍 **Quick Review**: Rapidly navigate long meetings or webinars for specific insights. +
--- -## 🧭 Quick Links & Navigation -| Topic | Resource / Section | Description | -| :--- | :--- | :--- | -| 🏗 **Architecture** | [**Technical Deep-Dive**](./docs/architecture.md) | Pipeline logic, intent nodes, and iterative generation. | -| 🎨 **UI & UX** | [**Design Overview**](./docs/ui_ux.md) | Frontend components, state, and user interaction flow. | -| 🚀 **Setup** | [**Setup Guide**](./docs/setup.md) | Prerequisites and environment installation instructions. | -| 📦 **Repository** | [**Deliverables**](#-deliverables) | Formal project components and file structure. | -| 🧠 **AI Logic** | [**The Pipeline**](#-the-ai-pipeline-highlights) | Logic overview of the 4-phase summarization engine. | -| 🛠 **Verifiability** | [**Reproducibility**](#-reproducibility) | Ensuring consistent results across environments. | -| 📸 **Demo** | [**Final Screenshot**](#-final-snapshot) | Visual overview of the chat and video editor interface. | +## 🚀 The Three Pillars of FrameFlow ---- +FrameFlow is built on three core intelligence layers, designed for creators, researchers, and developers. -## 📦 Deliverables -This formal homework project delivers a complete production-grade ecosystem: -* **Production Code**: Electron desktop app written in Vue 3 & TypeScript. -* **AI Engine**: A 4-phase pipeline (Extraction, Intent, Generation, Assembly). -* **Reproduction Tools**: Download the [Sample Videos Folder](https://drive.google.com/drive/folders/1g2Cp533NPQPtngLvnCuP5T8PZNc-FTZK?usp=sharing) (includes full and short versions) and use the app for a 4-phase trace. -* **Visual Documentation**: Fully documented [Architecture](./docs/architecture.md) and [UI/UX Flow](./docs/ui_ux.md). +### 1. 🎞️ Video to Short Video +Transform long-form content into concise, meaningful highlights. +- **Academic Precision**: Condense 2-hour technical lectures into 5-minute study guides. +- **Meeting Recap**: Rapidly navigate long webinars for specific insights. +- **Narrative Awareness**: AI understands scene transitions and audio context simultaneously. + +### 2. 📸 Video to Thumbnail +Extract and generate high-fidelity visual assets from any video source. +- **Auto-Enrichment**: AI analyzes scene quality to extract the most representative frames. +- **Professional Thumbnails**: Generate YouTube-ready or presentation-grade thumbnails with AI-driven composition. +- **Batch Processing**: Extract hundreds of scene-indexed images in seconds. + +### 3. 🎨 Images to Image +Leverage multimodal prompts to transform existing images or generate new ones from scratch. +- **Visual Continuity**: Use existing frames as structural references for new generations. +- **Prompt-Driven Flow**: Refine images using natural language within a unified chat-graph interface. +- **Multimodal Fusion**: Combine video context with external image uploads for hybrid creativity. --- -## 🧠 The AI Pipeline (Highlights) +## 🧩 Supported Inputs -Our unique **4-Phase Engine** ensures that every summary is contextually accurate: -* **Intent Recognition**: Uses a "Brain" node to distinguish between chat and generation, preventing token waste. -* **Iterative Refinement**: Supports an **Edit Mode** that performs a technical "diff" on previous timelines for perfect consistency. -* **Multimodal Fusion**: Processes visual scene transitions, audio transcripts, and user context simultaneously. +FrameFlow handles a wide range of media formats and sources: -> [!TIP] -> **Deep Dive:** Check out the **[Architecture Deep-Dive](./docs/architecture.md)** for Mermaid diagrams and logic breakdowns. +- **Video Formats**: Native support for `.mp4`, `.avi`, `.mov`, and `.webm`. +- **Online Sources**: YouTube, Google Drive, and direct media URLs (via `yt-dlp`). +- **Images**: High-fidelity `.jpg`, `.png`, and `.webp` for structural reference and multimodal generation. +- **Optimization**: High-res videos are automatically downscaled (480p) to ensure lightning-fast AI analysis without losing metadata. --- -## 🎨 UX Highlights -The interface is designed for **transparency** and **iterative control**: -* **Version History**: Switch between generated versions instantly to find the best cut. -* **Live Token Metrics**: Monitor AI usage costs and token counts in real-time. -* **Zero-Config Preprocessing**: Automatic scene detection and transcript extraction upon upload. +## 🎨 Premium Experience (UX) ---- +FrameFlow isn't just a tool; it's an iterative workspace: -## 🛠 Reproducibility -To guarantee identical behavior across different environments: -* **Sample Data**: Download our [Main Reference Videos Folder](https://drive.google.com/drive/folders/1g2Cp533NPQPtngLvnCuP5T8PZNc-FTZK?usp=sharing) (includes full and short versions) to test the pipeline. -* **JSON Enforcement**: Strict schemas ensure deterministic AI responses. -* **Precision Slicing**: FFmpeg settings calibrated for frame-accurate cuts. -* **Dependency Guard**: Locked environments via `package-lock.json` and `.npmrc`. -* **Reference Stability**: Edit mode always builds upon a fixed "Seed" timeline to avoid hallucinations. +- **Vue Flow Graph Interface**: Manage parallel tasks and version branches visually. +- **Live Metrics**: Monitor AI token usage and processing costs in real-time. +- **Zero-Config Preprocessing**: Automatic scene detection and transcript extraction. +- **Ambient Design**: A sleek, dark-mode-first interface with glassmorphism and smooth animations. --- -## 🚀 Getting Started -Check the **[Installation & Setup Guide](./docs/setup.md)** to configure: -1. **Environment**: Node.js and Gemini API Key. -2. **Tools**: FFmpeg and PySceneDetect for your OS. -3. **Launch**: `npm install && npm run dev`. +## 🧭 Navigation & Setup + +| Section | Link | Purpose | +| :--- | :--- | :--- | +| 🏗 **Architecture** | [**Deep-Dive**](./docs/architecture.md) | Pipeline logic, intent nodes, and iterative generation. | +| 🚀 **Installation** | [**Setup Guide**](./docs/setup.md) | Node.js, Gemini API, FFmpeg, and yt-dlp setup. | +| 🎨 **UI/UX** | [**Design Overview**](./docs/ui_ux.md) | Frontend components and interaction flow. | --- -## 📸 Final Snapshot +## 📸 Interface Preview +
- App Screenshot + FrameFlow Dashboard
--- -## 📜 License -Licensed under the MIT License - see [LICENSE](LICENSE) for details. +## 📜 License & Credits + +FrameFlow is licensed under the **MIT License**. Created by [navidshad](https://github.com/navidshad) and his classmates as part of a high-fidelity AI engineering initiative at Vilnius Gediminas Technical University (VGTU). + +--- + +> [!TIP] +> **Pro Choice:** Check the **[Architecture Deep-Dive](./docs/architecture.md)** to see how we handle multimodal intent recognition and technical "diffs" for consistency. diff --git a/docs/screenshots/01_dark-theme.jpg b/docs/screenshots/01_dark-theme.jpg new file mode 100644 index 0000000..b58295a Binary files /dev/null and b/docs/screenshots/01_dark-theme.jpg differ diff --git a/docs/screenshots/01_light-theme.jpg b/docs/screenshots/01_light-theme.jpg new file mode 100644 index 0000000..085446f Binary files /dev/null and b/docs/screenshots/01_light-theme.jpg differ diff --git a/docs/setup.md b/docs/setup.md index 78f4316..9e5569c 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -1,6 +1,6 @@ # 🛠 Installation & Setup Guide -This guide provides detailed instructions on how to set up the environment and run the **VGTU Video Summarization** application. +This guide provides detailed instructions on how to set up the environment and run the **FrameFlow** application. --- @@ -51,8 +51,8 @@ The application relies on **FFmpeg** for video processing and **PySceneDetect** 1. **Clone the repository**: ```bash - git clone https://github.com/navidshad/vgtu-video-summarization.git - cd vgtu-video-summarization + git clone https://github.com/navidshad/frameflow.git + cd frameflow ``` 2. **Install dependencies**: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9b5227b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,8340 @@ +{ + "name": "vgtu-video-summarization", + "version": "1.1.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vgtu-video-summarization", + "version": "1.1.6", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@google/genai": "^1.40.0", + "@tailwindcss/typography": "^0.5.19", + "@types/fluent-ffmpeg": "^2.1.28", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", + "@vueuse/core": "^10.9.0", + "ffmpeg-static": "^5.3.0", + "ffprobe-static": "^3.1.0", + "fluent-ffmpeg": "^2.1.3", + "markdown-it": "^14.1.1", + "pilotui": "^1.28.1", + "pinia": "^3.0.4", + "uuid": "^13.0.0", + "vue": "^3.4.21", + "vue-router": "^4.3.0", + "ytdlp-nodejs": "^3.4.4" + }, + "devDependencies": { + "@electron-toolkit/preload": "^3.0.0", + "@electron-toolkit/utils": "^3.0.0", + "@types/markdown-it": "^14.1.2", + "@types/node": "^25.2.3", + "@types/uuid": "^10.0.0", + "@vitejs/plugin-vue": "^5.0.4", + "autoprefixer": "^10.4.19", + "electron": "^28.2.0", + "electron-builder": "^24.13.3", + "electron-vite": "^2.1.0", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "typescript": "^5.4.2", + "vite": "^5.1.6", + "vue-tsc": "^2.0.6" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", + "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@derhuerst/http-basic": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/@derhuerst/http-basic/-/http-basic-8.2.4.tgz", + "integrity": "sha512-F9rL9k9Xjf5blCz8HsJRO4diy111cayL2vkY2XE4r4t3n0yPXVYy3KD3nJ1qbrSn9743UWSXH4IwuCa/HWlGFw==", + "license": "MIT", + "dependencies": { + "caseless": "^0.12.0", + "concat-stream": "^2.0.0", + "http-response-object": "^3.0.1", + "parse-cache-control": "^1.0.1" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron-toolkit/preload": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@electron-toolkit/preload/-/preload-3.0.2.tgz", + "integrity": "sha512-TWWPToXd8qPRfSXwzf5KVhpXMfONaUuRAZJHsKthKgZR/+LqX1dZVSSClQ8OTAEduvLGdecljCsoT2jSshfoUg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "electron": ">=13.0.0" + } + }, + "node_modules/@electron-toolkit/utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@electron-toolkit/utils/-/utils-3.0.0.tgz", + "integrity": "sha512-GaXHDhiT7KCvMJjXdp/QqpYinq69T/Pdl49Z1XLf8mKGf63dnsODMWyrmIjEQ0z/vG7dO8qF3fvmI6Eb2lUNZA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "electron": ">=13.0.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/@electron/asar/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/@electron/get/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/get/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/@electron/notarize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz", + "integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.0.5.tgz", + "integrity": "sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/universal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz", + "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.2.1", + "@malept/cross-spawn-promise": "^1.1.0", + "debug": "^4.3.1", + "dir-compare": "^3.0.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@google/genai": { + "version": "1.41.0", + "resolved": "https://registry.npmjs.org/@google/genai/-/genai-1.41.0.tgz", + "integrity": "sha512-S4WGil+PG0NBQRAx+0yrQuM/TWOLn2gGEy5wn4IsoOI6ouHad0P61p3OWdhJ3aqr9kfj8o904i/jevfaGoGuIQ==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^10.3.0", + "p-retry": "^7.1.1", + "protobufjs": "^7.5.4", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@modelcontextprotocol/sdk": "^1.25.2" + }, + "peerDependenciesMeta": { + "@modelcontextprotocol/sdk": { + "optional": true + } + } + }, + "node_modules/@headlessui/vue": { + "version": "1.7.23", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", + "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==", + "license": "MIT", + "dependencies": { + "@tanstack/vue-virtual": "^3.0.0-beta.60" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@storybook/builder-vite": { + "version": "8.6.15", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-8.6.15.tgz", + "integrity": "sha512-9Y05/ndZE6/eI7ZIUCD/QtH2htRIUs9j1gxE6oW0zRo9TJO1iqxfLNwgzd59KEkId7gdZxPei0l+LGTUGXYKRg==", + "license": "MIT", + "dependencies": { + "@storybook/csf-plugin": "8.6.15", + "browser-assert": "^1.2.1", + "ts-dedent": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.15", + "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, + "node_modules/@storybook/core": { + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/core/-/core-8.6.18.tgz", + "integrity": "sha512-dRBP2TnX6fGdS0T2mXBHjkS/3Nlu1ra1huovZVFuM67CYMzrhM/3hX/zru1vWSC5rqY93ZaAhjMciPW4pK5mMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@storybook/theming": "8.6.18", + "better-opn": "^3.0.2", + "browser-assert": "^1.2.1", + "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", + "esbuild-register": "^3.5.0", + "jsdoc-type-pratt-parser": "^4.0.0", + "process": "^0.11.10", + "recast": "^0.23.5", + "semver": "^7.6.2", + "util": "^0.12.5", + "ws": "^8.2.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/@storybook/core/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/csf-plugin": { + "version": "8.6.15", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-8.6.15.tgz", + "integrity": "sha512-ZLz/mtOoE1Jj2lE4pK3U7MmYrv5+lot3mGtwxGb832tcABMc97j9O+reCVxZYc7DeFbBuuEdMT9rBL/O3kXYmw==", + "license": "MIT", + "dependencies": { + "unplugin": "^1.3.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.6.15" + } + }, + "node_modules/@storybook/theming": { + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-8.6.18.tgz", + "integrity": "sha512-n6OEjEtHupa2PdTwWzRepr7cO8NkDd4rgF6BKLitRbujOspLxzMBEqdphs+QLcuiCIgf33SqmEA64QWnbSMhPw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz", + "integrity": "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/vue-virtual": { + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.18.tgz", + "integrity": "sha512-6pT8HdHtTU5Z+t906cGdCroUNA5wHjFXsNss9gwk7QAr1VNZtz9IQCs2Nhx0gABK48c+OocHl2As+TMg8+Hy4A==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "vue": "^2.7.0 || ^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "license": "ISC", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/fluent-ffmpeg": { + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/@types/fluent-ffmpeg/-/fluent-ffmpeg-2.1.28.tgz", + "integrity": "sha512-5ovxsDwBcPfJ+eYs1I/ZpcYCnkce7pvH9AHSvrZllAp1ZPpTRDZAFjF3TRFbukxSgIYTTNYePbS0rKUmaxVbXw==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.2.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", + "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue-flow/background": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@vue-flow/background/-/background-1.3.2.tgz", + "integrity": "sha512-eJPhDcLj1wEo45bBoqTXw1uhl0yK2RaQGnEINqvvBsAFKh/camHJd5NPmOdS1w+M9lggc9igUewxaEd3iCQX2w==", + "license": "MIT", + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, + "node_modules/@vue-flow/controls": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@vue-flow/controls/-/controls-1.1.3.tgz", + "integrity": "sha512-XCf+G+jCvaWURdFlZmOjifZGw3XMhN5hHlfMGkWh9xot+9nH9gdTZtn+ldIJKtarg3B21iyHU8JjKDhYcB6JMw==", + "license": "MIT", + "peerDependencies": { + "@vue-flow/core": "^1.23.0", + "vue": "^3.3.0" + } + }, + "node_modules/@vue-flow/core": { + "version": "1.48.2", + "resolved": "https://registry.npmjs.org/@vue-flow/core/-/core-1.48.2.tgz", + "integrity": "sha512-raxhgKWE+G/mcEvXJjGFUDYW9rAI3GOtiHR3ZkNpwBWuIaCC1EYiBmKGwJOoNzVFgwO7COgErnK7i08i287AFA==", + "license": "MIT", + "dependencies": { + "@vueuse/core": "^10.5.0", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.28.tgz", + "integrity": "sha512-kviccYxTgoE8n6OCw96BNdYlBg2GOWfBuOW4Vqwrt7mSKWKwFVvI8egdTltqRgITGPsTFYtKYfxIG8ptX2PJHQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.28", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.28.tgz", + "integrity": "sha512-/1ZepxAb159jKR1btkefDP+J2xuWL5V3WtleRmxaT+K2Aqiek/Ab/+Ebrw2pPj0sdHO8ViAyyJWfhXXOP/+LQA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.28.tgz", + "integrity": "sha512-6TnKMiNkd6u6VeVDhZn/07KhEZuBSn43Wd2No5zaP5s3xm8IqFTHBj84HJah4UepSUJTro5SoqqlOY22FKY96g==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.28", + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.28.tgz", + "integrity": "sha512-JCq//9w1qmC6UGLWJX7RXzrGpKkroubey/ZFqTpvEIDJEKGgntuDMqkuWiZvzTzTA5h2qZvFBFHY7fAAa9475g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.28.tgz", + "integrity": "sha512-gr5hEsxvn+RNyu9/9o1WtdYdwDjg5FgjUSBEkZWqgTKlo/fvwZ2+8W6AfKsc9YN2k/+iHYdS9vZYAhpi10kNaw==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.28.tgz", + "integrity": "sha512-POVHTdbgnrBBIpnbYU4y7pOMNlPn2QVxVzkvEA2pEgvzbelQq4ZOUxbp2oiyo+BOtiYlm8Q44wShHJoBvDPAjQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.28", + "@vue/shared": "3.5.28" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.28.tgz", + "integrity": "sha512-4SXxSF8SXYMuhAIkT+eBRqOkWEfPu6nhccrzrkioA6l0boiq7sp18HCOov9qWJA5HML61kW8p/cB4MmBiG9dSA==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.28", + "@vue/runtime-core": "3.5.28", + "@vue/shared": "3.5.28", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.28.tgz", + "integrity": "sha512-pf+5ECKGj8fX95bNincbzJ6yp6nyzuLDhYZCeFxUNp8EBrQpPpQaLX3nNCp49+UbgbPun3CeVE+5CXVV1Xydfg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.28", + "@vue/shared": "3.5.28" + }, + "peerDependencies": { + "vue": "3.5.28" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.28.tgz", + "integrity": "sha512-cfWa1fCGBxrvaHRhvV3Is0MgmrbSCxYTXCSCau2I0a1Xw1N1pHAvkWCiXPRAqjvToILvguNyEwjevUqAuBQWvQ==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/@vueuse/shared": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", + "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/head": { + "version": "0.9.8", + "resolved": "https://registry.npmjs.org/@vueuse/head/-/head-0.9.8.tgz", + "integrity": "sha512-zt8+JksoVFKxRvmABlaUHA62w+8nOcD8cJnaJ0+SHcr6xaIP3GXgh7/n2TzUoWw4l3d9UxRNs+tapgHdsQ7RbA==", + "license": "MIT", + "dependencies": { + "@vueuse/shared": "^9.3.0", + "@zhead/schema": "^0.8.5", + "@zhead/schema-vue": "^0.8.5" + }, + "peerDependencies": { + "vue": ">=2.7 || >=3" + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-9.13.0.tgz", + "integrity": "sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==", + "license": "MIT", + "dependencies": { + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@zhead/schema": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@zhead/schema/-/schema-0.8.5.tgz", + "integrity": "sha512-1S3Otr2zpl1zwP72dNseVXQNG9tnTQ6hHUEUYwINvBjRj6bHcUwdE+Itc9OLxnGAJT/7p8P7GHGo5sshXJNJsA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@zhead/schema-raw": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@zhead/schema-raw/-/schema-raw-0.8.5.tgz", + "integrity": "sha512-Aq+9mksf5zbtj7HYluT6PVyfpQ6z7mja9MzjFxg76Vt+Q9i0oL1XN6ZYaCXImWRafwbyAxjFQ5aUCVyFn79OpA==", + "license": "MIT", + "dependencies": { + "@zhead/schema": "0.8.5" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + } + }, + "node_modules/@zhead/schema-vue": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@zhead/schema-vue/-/schema-vue-0.8.5.tgz", + "integrity": "sha512-6aXjYy3fZVeYBLrHcJQqzqwzC/2tafRO5UxZEgBHnryRnzeLNZV6nTptDvIPWiJObMoJTK21vbg3gkfLNQg84g==", + "license": "MIT", + "dependencies": { + "@vueuse/shared": "^9.2.0", + "@zhead/schema": "0.8.5", + "@zhead/schema-raw": "0.8.5" + }, + "funding": { + "url": "https://github.com/sponsors/harlan-zw" + }, + "peerDependencies": { + "vue": ">=2.7 || >=3" + } + }, + "node_modules/7zip-bin": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", + "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/app-builder-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz", + "integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/notarize": "2.2.1", + "@electron/osx-sign": "1.0.5", + "@electron/universal": "1.5.1", + "@malept/flatpak-bundler": "^0.4.0", + "@types/fs-extra": "9.0.13", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.3.4", + "ejs": "^3.1.8", + "electron-publish": "24.13.1", + "form-data": "^4.0.0", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "is-ci": "^3.0.0", + "isbinaryfile": "^5.0.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "minimatch": "^5.1.1", + "read-config-file": "6.3.2", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.8", + "tar": "^6.1.12", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "24.13.3", + "electron-builder-squirrel-windows": "24.13.3" + } + }, + "node_modules/app-builder-lib/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/archiver": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", + "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^2.1.0", + "async": "^3.2.4", + "buffer-crc32": "^0.2.1", + "readable-stream": "^3.6.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^2.2.0", + "zip-stream": "^4.1.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/archiver-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", + "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.1.4", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^2.0.0" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/archiver-utils/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/archiver-utils/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/archiver-utils/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/archiver-utils/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/archiver/node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/better-opn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz", + "integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "open": "^8.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-assert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", + "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==" + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", + "integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "7zip-bin": "~5.2.0", + "app-builder-bin": "4.0.0", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.3", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz", + "integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-response/node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/compress-commons": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", + "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-crc32": "^0.2.13", + "crc32-stream": "^4.0.2", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/config-file-ts": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz", + "integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^10.3.10", + "typescript": "^5.3.3" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/crc32-stream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", + "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "crc-32": "^1.2.0", + "readable-stream": "^3.4.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css-declaration-sorter": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", + "integrity": "sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==", + "license": "ISC", + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, + "node_modules/css-select": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", + "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.0.1", + "domhandler": "^4.3.1", + "domutils": "^2.8.0", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssnano": { + "version": "5.1.15", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.15.tgz", + "integrity": "sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==", + "license": "MIT", + "dependencies": { + "cssnano-preset-default": "^5.2.14", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.14.tgz", + "integrity": "sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==", + "license": "MIT", + "dependencies": { + "css-declaration-sorter": "^6.3.1", + "cssnano-utils": "^3.1.0", + "postcss-calc": "^8.2.3", + "postcss-colormin": "^5.3.1", + "postcss-convert-values": "^5.1.3", + "postcss-discard-comments": "^5.1.2", + "postcss-discard-duplicates": "^5.1.0", + "postcss-discard-empty": "^5.1.1", + "postcss-discard-overridden": "^5.1.0", + "postcss-merge-longhand": "^5.1.7", + "postcss-merge-rules": "^5.1.4", + "postcss-minify-font-values": "^5.1.0", + "postcss-minify-gradients": "^5.1.1", + "postcss-minify-params": "^5.1.4", + "postcss-minify-selectors": "^5.2.1", + "postcss-normalize-charset": "^5.1.0", + "postcss-normalize-display-values": "^5.1.0", + "postcss-normalize-positions": "^5.1.1", + "postcss-normalize-repeat-style": "^5.1.1", + "postcss-normalize-string": "^5.1.0", + "postcss-normalize-timing-functions": "^5.1.0", + "postcss-normalize-unicode": "^5.1.1", + "postcss-normalize-url": "^5.1.0", + "postcss-normalize-whitespace": "^5.1.1", + "postcss-ordered-values": "^5.1.3", + "postcss-reduce-initial": "^5.1.2", + "postcss-reduce-transforms": "^5.1.0", + "postcss-svgo": "^5.1.0", + "postcss-unique-selectors": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", + "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/cssnano/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "license": "MIT", + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dir-compare": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", + "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "^1.0.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/dmg-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz", + "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "fs-extra": "^10.1.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.11" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "28.3.3", + "resolved": "https://registry.npmjs.org/electron/-/electron-28.3.3.tgz", + "integrity": "sha512-ObKMLSPNhomtCOBAxFS8P2DW/4umkh72ouZUlUKzXGtYuPzgr1SYhskhFWgzAsPtUzhL2CzyV2sfbHcEW4CXqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^18.11.18", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz", + "integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "dmg-builder": "24.13.3", + "fs-extra": "^10.1.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "read-config-file": "6.3.2", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-24.13.3.tgz", + "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "24.13.3", + "archiver": "^5.3.1", + "builder-util": "24.13.1", + "fs-extra": "^10.1.0" + } + }, + "node_modules/electron-publish": { + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", + "integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", + "chalk": "^4.1.2", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "license": "ISC" + }, + "node_modules/electron-vite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz", + "integrity": "sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "cac": "^6.7.14", + "esbuild": "^0.21.5", + "magic-string": "^0.30.10", + "picocolors": "^1.0.1" + }, + "bin": { + "electron-vite": "bin/electron-vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@swc/core": "^1.0.0", + "vite": "^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + } + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "license": "MIT", + "peer": true, + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "peer": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "license": "Apache-2.0" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/ffmpeg-static": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ffmpeg-static/-/ffmpeg-static-5.3.0.tgz", + "integrity": "sha512-H+K6sW6TiIX6VGend0KQwthe+kaceeH/luE8dIZyOP35ik7ahYojDuqlTV1bOrtEwl01sy2HFNGQfi5IDJvotg==", + "hasInstallScript": true, + "license": "GPL-3.0-or-later", + "dependencies": { + "@derhuerst/http-basic": "^8.2.0", + "env-paths": "^2.2.0", + "https-proxy-agent": "^5.0.0", + "progress": "^2.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/ffprobe-static": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ffprobe-static/-/ffprobe-static-3.1.0.tgz", + "integrity": "sha512-Dvpa9uhVMOYivhHKWLGDoa512J751qN1WZAIO+Xw4L/mrUSPxS4DApzSUDbCFE/LUq2+xYnznEahTd63AqBSpA==", + "license": "MIT" + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fluent-ffmpeg": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", + "integrity": "sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==", + "license": "MIT", + "dependencies": { + "async": "^0.2.9", + "which": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/google-auth-library": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-logging-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gtoken": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", + "license": "MIT", + "dependencies": { + "gaxios": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http-response-object": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", + "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", + "license": "MIT", + "dependencies": { + "@types/node": "^10.0.3" + } + }, + "node_modules/http-response-object/node_modules/@types/node": { + "version": "10.17.60", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", + "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", + "license": "MIT" + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.8.0.tgz", + "integrity": "sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lazystream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 0.6.3" + } + }, + "node_modules/lazystream/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/lazystream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lazystream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.difference": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", + "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "license": "MIT" + }, + "node_modules/lodash.union": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", + "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "license": "CC0-1.0" + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimatch/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", + "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/parchment": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz", + "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==", + "license": "BSD-3-Clause" + }, + "node_modules/parse-cache-control": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", + "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/perfect-scrollbar": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.6.tgz", + "integrity": "sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pilotui": { + "version": "1.28.1", + "resolved": "https://registry.npmjs.org/pilotui/-/pilotui-1.28.1.tgz", + "integrity": "sha512-S9MJt00HEBUUBk775bOSTWkkkVjjce7lFsvOC+q0ovqfAo3/x0vGHkSUyzNrj2nelAyoZQQuCYYsA1Pn20Ylow==", + "dependencies": { + "@headlessui/vue": "^1.7.23", + "@storybook/builder-vite": "^8.4.5", + "@vueuse/head": "^0.9.7", + "pinia": "^2.0.22", + "quill": "^2.0.2", + "sweetalert2": "^11.15.3", + "vue": "^3.5.12", + "vue-collapsed": "^1.3.4", + "vue-router": "^4.4.5", + "vue3-perfect-scrollbar": "^1.6.0", + "vue3-popper": "^1.5.0" + } + }, + "node_modules/pilotui/node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia/node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-calc": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", + "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.9", + "postcss-value-parser": "^4.2.0" + }, + "peerDependencies": { + "postcss": "^8.2.2" + } + }, + "node_modules/postcss-colormin": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.1.tgz", + "integrity": "sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-convert-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.3.tgz", + "integrity": "sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-comments": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", + "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-duplicates": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", + "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", + "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", + "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.7.tgz", + "integrity": "sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "stylehacks": "^5.1.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.4.tgz", + "integrity": "sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^3.1.0", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", + "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", + "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", + "license": "MIT", + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.4.tgz", + "integrity": "sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", + "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", + "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", + "license": "MIT", + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", + "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", + "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", + "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", + "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", + "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.1.tgz", + "integrity": "sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", + "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", + "license": "MIT", + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", + "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", + "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", + "license": "MIT", + "dependencies": { + "cssnano-utils": "^3.1.0", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz", + "integrity": "sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", + "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-svgo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", + "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.2.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", + "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/quill": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz", + "integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==", + "license": "BSD-3-Clause", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash-es": "^4.17.21", + "parchment": "^3.0.0", + "quill-delta": "^5.1.0" + }, + "engines": { + "npm": ">=8.2.3" + } + }, + "node_modules/quill-delta": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", + "license": "MIT", + "dependencies": { + "fast-diff": "^1.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-config-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", + "integrity": "sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-file-ts": "^0.2.4", + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdir-glob": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", + "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "minimatch": "^5.1.0" + } + }, + "node_modules/readdir-glob/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "peer": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "license": "MIT" + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/storybook": { + "version": "8.6.18", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-8.6.18.tgz", + "integrity": "sha512-p8seiSI6FiVY6P3V0pG+5v7c8pDMehMAFRWEhG5XqIBSQszzOjDnW2rNvm3odoLKfo3V3P6Cs6Hv9ILzymULyQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@storybook/core": "8.6.18" + }, + "bin": { + "getstorybook": "bin/index.cjs", + "sb": "bin/index.cjs", + "storybook": "bin/index.cjs" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "prettier": "^2 || ^3" + }, + "peerDependenciesMeta": { + "prettier": { + "optional": true + } + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stylehacks": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", + "integrity": "sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.21.4", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "license": "MIT", + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/sweetalert2": { + "version": "11.26.18", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.26.18.tgz", + "integrity": "sha512-3O5feBqV+hTIOwCRKGuZGHosjiuBAKP/vpBl6vKFZeVYfCUGdXqXuuidn6YXHan3f6e62UdmnjwJBt8UtDVBhg==", + "license": "MIT", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/limonte" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT", + "peer": true + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.28", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.28.tgz", + "integrity": "sha512-BRdrNfeoccSoIZeIhyPBfvWSLFP4q8J3u8Ju8Ug5vu3LdD+yTM13Sg4sKtljxozbnuMu1NB1X5HBHRYUzFocKg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.28", + "@vue/compiler-sfc": "3.5.28", + "@vue/runtime-dom": "3.5.28", + "@vue/server-renderer": "3.5.28", + "@vue/shared": "3.5.28" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-collapsed": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/vue-collapsed/-/vue-collapsed-1.3.5.tgz", + "integrity": "sha512-U6wCa4mFpaX2Fr9BWtGNPte3SAgtpk1NjeS/NRLHDHu2fDs3/MQ3W13pvWXy5BGbtz14HxzSq6efC9WrHblozQ==", + "license": "MIT" + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/vue3-perfect-scrollbar": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vue3-perfect-scrollbar/-/vue3-perfect-scrollbar-1.6.1.tgz", + "integrity": "sha512-r9wfxlFwVyHXMPKG0PnR7fDfJPQ20KEVzKQfSU5by2WKYz2PwV0bTfyfejmEyZXsXL0O8VtSWtgxfPuFR2AGOg==", + "license": "MIT", + "dependencies": { + "cssnano": "^5.1.14", + "perfect-scrollbar": "^1.5.5", + "postcss-import": "^12.0.0" + } + }, + "node_modules/vue3-perfect-scrollbar/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "license": "ISC" + }, + "node_modules/vue3-perfect-scrollbar/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "license": "MIT", + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/vue3-perfect-scrollbar/node_modules/postcss-import": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-12.0.1.tgz", + "integrity": "sha512-3Gti33dmCjyKBgimqGxL3vcV8w9+bsHwO5UrBawp796+jdardbcFl4RP5w/76BwNL7aGzpKstIfF9I+kdE8pTw==", + "license": "MIT", + "dependencies": { + "postcss": "^7.0.1", + "postcss-value-parser": "^3.2.3", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/vue3-perfect-scrollbar/node_modules/postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "license": "MIT" + }, + "node_modules/vue3-popper": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/vue3-popper/-/vue3-popper-1.5.0.tgz", + "integrity": "sha512-xaEnx90YBnlSg5G2yWqm2DHWHg+DB99UVRp4VsyTF0QLXyHrqSuE1Xo5+sG0AQq/lBcrGMlk5NU5xE2MDLKViw==", + "license": "MIT", + "dependencies": { + "@popperjs/core": "^2.9.2", + "debounce": "^1.2.1" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "vue": "^3.2.20" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "peer": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/ytdlp-nodejs": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/ytdlp-nodejs/-/ytdlp-nodejs-3.4.4.tgz", + "integrity": "sha512-PR2nQOtyOzSuSQpWf6SHD97d6qlcfY2y6CVCeYnuDacYDc1YzNLXYiqAyIpFWk13UUrtQN+XD35h5DERli3SXQ==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "ytdlp": "dist/cli/index.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/zip-stream": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", + "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "archiver-utils": "^3.0.4", + "compress-commons": "^4.1.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/archiver-utils": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", + "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "glob": "^7.2.3", + "graceful-fs": "^4.2.0", + "lazystream": "^1.0.0", + "lodash.defaults": "^4.2.0", + "lodash.difference": "^4.5.0", + "lodash.flatten": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.union": "^4.6.0", + "normalize-path": "^3.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/zip-stream/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + } + } +} diff --git a/package.json b/package.json index 7c78a43..46fa79a 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "vgtu-video-summarization", - "version": "1.1.5", + "name": "frameflow", + "version": "1.1.6", "type": "module", - "description": "AI-based Video Summarizer", + "description": "AI-powered multimodal video and image processing platform.", "main": "./out/main/index.js", "author": "Antigravity", "license": "MIT", @@ -25,6 +25,9 @@ "@google/genai": "^1.40.0", "@tailwindcss/typography": "^0.5.19", "@types/fluent-ffmpeg": "^2.1.28", + "@vue-flow/background": "^1.3.2", + "@vue-flow/controls": "^1.1.3", + "@vue-flow/core": "^1.48.2", "@vueuse/core": "^10.9.0", "ffmpeg-static": "^5.3.0", "ffprobe-static": "^3.1.0", @@ -34,7 +37,8 @@ "pinia": "^3.0.4", "uuid": "^13.0.0", "vue": "^3.4.21", - "vue-router": "^4.3.0" + "vue-router": "^4.3.0", + "ytdlp-nodejs": "^3.4.4" }, "devDependencies": { "@electron-toolkit/preload": "^3.0.0", @@ -54,8 +58,8 @@ "vue-tsc": "^2.0.6" }, "build": { - "appId": "com.vgtu.video-summarization", - "productName": "VGTU Video Summarizer", + "appId": "com.frameflow.app", + "productName": "FrameFlow", "directories": { "output": "dist" }, diff --git a/src/main/constants/gemini.ts b/src/main/constants/gemini.ts index c3ca1c3..e44551f 100644 --- a/src/main/constants/gemini.ts +++ b/src/main/constants/gemini.ts @@ -4,6 +4,7 @@ export const GEMINI_MODEL_2_5_PRO = 'gemini-2.5-pro' export const GEMINI_MODEL_2_5_FLASH = 'gemini-2.5-flash' export const GEMINI_MODEL_2_5_FLASH_LITE = 'gemini-2.5-flash-lite' export const GEMINI_MODEL_3_FLASH_PREVIEW = 'gemini-3-flash-preview' +export const GEMINI_MODEL_3_1_FLASH_IMAGE_PREVIEW = 'gemini-3.1-flash-image-preview' export const DEFAULT_MODEL_SETTINGS: ModelSettings = { pricing: { @@ -48,6 +49,15 @@ export const DEFAULT_MODEL_SETTINGS: ModelSettings = { output: { standard: 3.00 } + }, + [GEMINI_MODEL_3_1_FLASH_IMAGE_PREVIEW]: { + input: { + standard: 0.50 + }, + output: { + standard: 3.00, + image: 0.0672 + } } }, selection: { @@ -55,6 +65,12 @@ export const DEFAULT_MODEL_SETTINGS: ModelSettings = { 'corrected-transcript': GEMINI_MODEL_2_5_PRO, 'intent': GEMINI_MODEL_2_5_FLASH, 'timeline-new': GEMINI_MODEL_3_FLASH_PREVIEW, - 'timeline-edit': GEMINI_MODEL_2_5_FLASH + 'timeline-edit': GEMINI_MODEL_2_5_FLASH, + 'thumbnail': GEMINI_MODEL_3_1_FLASH_IMAGE_PREVIEW, + 'scene-description': GEMINI_MODEL_2_5_FLASH_LITE, + 'image-extraction': GEMINI_MODEL_2_5_FLASH_LITE, + 'image-intent': GEMINI_MODEL_2_5_FLASH, + 'image-generation': GEMINI_MODEL_3_1_FLASH_IMAGE_PREVIEW, + 'image-upscale': GEMINI_MODEL_3_1_FLASH_IMAGE_PREVIEW } } diff --git a/src/main/constants/paths.ts b/src/main/constants/paths.ts new file mode 100644 index 0000000..0122318 --- /dev/null +++ b/src/main/constants/paths.ts @@ -0,0 +1,10 @@ +export const THREAD_DIRS = { + IMAGES: 'images', + FRAMES: 'frames', + ANALYSIS: 'analysis', + GENERATED_IMAGES: 'generated-images', + GENERATED_VIDEOS: 'generated-videos', + AUDIO: 'audio', + VIDEO: 'video', + TRANSCRIPTS: 'transcripts' +} as const diff --git a/src/main/ffmpeg/index.ts b/src/main/ffmpeg/index.ts index 7ed40e3..2e0fc12 100644 --- a/src/main/ffmpeg/index.ts +++ b/src/main/ffmpeg/index.ts @@ -9,10 +9,17 @@ const IS_MAC = process.platform === 'darwin' -// Set path to ffmpeg and ffprobe binaries -if (ffmpegPath) { +/** + * Returns the absolute path to the unpacked ffmpeg binary. + */ +export function getFFmpegBinaryPath(): string { const p = typeof ffmpegPath === 'string' ? ffmpegPath : (ffmpegPath as any).path - ffmpeg.setFfmpegPath(p.replace('app.asar', 'app.asar.unpacked')) + return p.replace('app.asar', 'app.asar.unpacked') +} + +// Initializing ffmpeg and ffprobe paths +if (ffmpegPath) { + ffmpeg.setFfmpegPath(getFFmpegBinaryPath()) } if (ffprobePath) { const p = typeof ffprobePath === 'string' ? ffprobePath : (ffprobePath as any).path @@ -88,6 +95,41 @@ export async function getVideoDuration(filePath: string): Promise { }) } +/** + * Gets comprehensive metadata for a video file. + */ +export async function getVideoMetadata(filePath: string): Promise { + return new Promise((resolve, reject) => { + ffmpeg.ffprobe(filePath, (err, metadata) => { + if (err) return reject(err) + const videoStream = metadata.streams.find((s) => s.codec_type === 'video') + const audioStream = metadata.streams.find((s) => s.codec_type === 'audio') + + if (!videoStream) { + return reject(new Error('No video stream found')) + } + + // Parse FPS + let fps = 0 + if (videoStream.r_frame_rate) { + const [num, den] = videoStream.r_frame_rate.split('/').map(Number) + fps = num / den + } + + resolve({ + duration: metadata.format.duration || 0, + width: videoStream.width || 0, + height: videoStream.height || 0, + size: metadata.format.size || 0, + codec: videoStream.codec_name || 'unknown', + fps: Math.round(fps * 100) / 100, + format: metadata.format.format_name || 'unknown', + hasAudio: !!audioStream + }) + }) + }) +} + /** * Returns true if the video resolution is 480p or lower. */ @@ -107,13 +149,17 @@ export async function isVideoLowResolution(filePath: string): Promise { export async function toLowResolution( filePath: string, outputDir: string, - onProgress?: (percent: number) => void + onProgress?: (percent: number) => void, + signal?: AbortSignal ): Promise { const ext = extname(filePath).toLowerCase() const rawFilename = basename(filePath, extname(filePath)) const filename = sanitizeFilename(rawFilename) const outputPath = join(outputDir, `${filename}_480p${ext}`) + if (signal?.aborted) { + throw new Error('FFmpeg downscaling aborted by user before start') + } return new Promise((resolve, reject) => { const isWebm = ext === '.webm' @@ -152,6 +198,13 @@ export async function toLowResolution( ]) } + if (signal) { + signal.addEventListener('abort', () => { + console.log('FFmpeg toLowResolution aborted by signal') + command.kill('SIGKILL') + }) + } + command .output(outputPath) .on('progress', (progress) => { @@ -161,6 +214,9 @@ export async function toLowResolution( }) .on('end', () => resolve(outputPath)) .on('error', (err, stdout, stderr) => { + if (signal?.aborted) { + return reject(new Error('FFmpeg downscaling aborted by user')) + } console.error('FFmpeg toLowResolution error:', err) console.error('FFmpeg stderr:', stderr) reject(new Error(`FFmpeg failed: ${err.message}. ${stderr}`)) @@ -175,15 +231,19 @@ export async function toLowResolution( export async function toAudio( filePath: string, outputDir: string, - onProgress?: (percent: number) => void + onProgress?: (percent: number) => void, + signal?: AbortSignal ): Promise { const rawFilename = basename(filePath, extname(filePath)) const filename = sanitizeFilename(rawFilename) const outputPath = join(outputDir, `${filename}.mp3`) + if (signal?.aborted) { + throw new Error('FFmpeg audio extraction aborted by user before start') + } return new Promise((resolve, reject) => { - ffmpeg(filePath) + const command = ffmpeg(filePath) .toFormat('mp3') .output(outputPath) .on('progress', (progress) => { @@ -192,8 +252,21 @@ export async function toAudio( } }) .on('end', () => resolve(outputPath)) - .on('error', (err) => reject(err)) - .run() + .on('error', (err) => { + if (signal?.aborted) { + return reject(new Error('FFmpeg audio extraction aborted by user')) + } + reject(err) + }) + + if (signal) { + signal.addEventListener('abort', () => { + console.log('FFmpeg toAudio aborted by signal') + command.kill('SIGKILL') + }) + } + + command.run() }) } @@ -206,7 +279,8 @@ export async function assembleVideo( segments: TimelineSegment[], outputDir: string, messageId: string, - onProgress?: (percent: number) => void + onProgress?: (percent: number) => void, + signal?: AbortSignal ): Promise { const ext = extname(videoPath) const rawFilename = basename(videoPath, ext) @@ -218,6 +292,10 @@ export async function assembleVideo( throw new Error('No segments provided for assembly') } + if (signal?.aborted) { + throw new Error('FFmpeg video assembly aborted by user before start') + } + // Helper to parse SRT-style time string or simplified HH:MM:SS to seconds const timeToSeconds = (t: string): number => { const [time, milli] = t.split(/[.,]/) @@ -308,6 +386,13 @@ export async function assembleVideo( outputOptions.push('-movflags', '+faststart') } + if (signal) { + signal.addEventListener('abort', () => { + console.log('FFmpeg assembleVideo aborted by signal') + command.kill('SIGKILL') + }) + } + command .output(outputPath) .outputOptions(outputOptions) @@ -324,6 +409,9 @@ export async function assembleVideo( resolve(outputPath) }) .on('error', (err, stdout, stderr) => { + if (signal?.aborted) { + return reject(new Error('FFmpeg video assembly aborted by user')) + } console.error('FFmpeg assembly error:', err) console.error('FFmpeg stderr:', stderr) reject(new Error(`FFmpeg assembly failed: ${err.message}. ${stderr}`)) @@ -340,15 +428,20 @@ export async function assembleVideo( export async function extractFrame( videoPath: string, timestamp: number, - outputDir: string + outputDir: string, + signal?: AbortSignal ): Promise { const ext = extname(videoPath) const rawFilename = basename(videoPath, ext) const filename = sanitizeFilename(rawFilename) const outputPath = join(outputDir, `${filename}_frame_${timestamp.toFixed(2)}.jpg`) + if (signal?.aborted) { + throw new Error('FFmpeg frame extraction aborted by user before start') + } + return new Promise((resolve, reject) => { - ffmpeg(videoPath) + const command = ffmpeg(videoPath) .seekInput(timestamp) .outputOptions([ '-vframes', '1', @@ -358,9 +451,20 @@ export async function extractFrame( .output(outputPath) .on('end', () => resolve(outputPath)) .on('error', (err) => { + if (signal?.aborted) { + return reject(new Error('FFmpeg frame extraction aborted by user')) + } console.error(`Error extracting frame at ${timestamp}:`, err) reject(err) }) - .run() + + if (signal) { + signal.addEventListener('abort', () => { + console.log('FFmpeg extractFrame aborted by signal') + command.kill('SIGKILL') + }) + } + + command.run() }) } diff --git a/src/main/gemini/adapter.ts b/src/main/gemini/adapter.ts index 7bc169a..9230446 100644 --- a/src/main/gemini/adapter.ts +++ b/src/main/gemini/adapter.ts @@ -1,5 +1,5 @@ import { GoogleGenAI, type GenerateContentParameters } from '@google/genai'; -import { Usage, UsageRecord } from '../../shared/types'; +import { Usage, UsageRecord, UpscaleFactor } from '../../shared/types'; import { settingsManager } from '../settings'; import * as fs from 'fs'; import * as path from 'path'; @@ -12,7 +12,7 @@ export class GeminiAdapter { constructor(apiKey: string) { this.client = new GoogleGenAI({ apiKey, - apiVersion: 'v1alpha' // Using v1alpha for latest features like thinking + apiVersion: 'v1beta' // required for gemini-3.1-flash-image-preview }); } @@ -37,13 +37,96 @@ export class GeminiAdapter { }; } + private extractResultText(response: any): string { + const candidate = response.candidates?.[0]; + if (!candidate || !candidate.content || !candidate.content.parts) { + return ''; + } + + // Filter out parts that are explicitly marked as thoughts + const resultParts = candidate.content.parts.filter((p: any) => !p.thought && p.text); + + // If no non-thought text parts found, but there are text parts, + // it might be that the SDK didn't mark them but we have multiple. + // Usually the last text part is the result. + if (resultParts.length === 0) { + const allTextParts = candidate.content.parts.filter((p: any) => p.text); + return allTextParts.length > 0 ? allTextParts[allTextParts.length - 1].text : ''; + } + + return resultParts.map((p: any) => p.text).join('\n'); + } + + private async withRetry( + operation: () => Promise, + signal?: AbortSignal, + maxRetries: number = 3 + ): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await operation(); + } catch (error: any) { + if (signal?.aborted) throw error; + + const isTransient = this.isTransientError(error); + if (!isTransient || attempt === maxRetries) { + throw error; + } + + const delay = Math.pow(2, attempt) * 1000; + console.warn(`[GEMINI ADAPTER] Attempt ${attempt} failed. Retrying in ${delay}ms... Status: ${error.status || 'Unknown'}. Message: ${error.message}`); + + await new Promise(resolve => { + const timer = setTimeout(resolve, delay); + if (signal) { + signal.addEventListener('abort', () => { + clearTimeout(timer); + resolve(null); + }, { once: true }); + } + }); + + if (signal?.aborted) throw new Error('Operation aborted during retry backoff'); + } + } + throw new Error('Unexpected fallback in withRetry'); + } + + private isTransientError(error: any): boolean { + const message = (error.message || '').toLowerCase(); + const status = (error.status || (error.error && error.error.code) || '').toString(); + const code = (error.code || error.cause?.code || '').toString(); + + const transientStatuses = ['503', '429', 'UNAVAILABLE', 'RESOURCE_EXHAUSTED', 'DEADLINE_EXCEEDED']; + if (transientStatuses.includes(status)) return true; + + const transientCodes = ['UND_ERR_HEADERS_TIMEOUT', 'ECONNRESET', 'ETIMEDOUT', 'ENOTFOUND']; + if (transientCodes.includes(code)) return true; + + if ( + message.includes('503') || + message.includes('429') || + message.includes('high demand') || + message.includes('too many requests') || + message.includes('service unavailable') || + message.includes('deadline exceeded') || + message.includes('fetch failed') || + message.includes('timeout') + ) { + return true; + } + + return false; + } + /** * Generates a non-structured result from Gemini. */ async generateText( modelName: string, userPrompt: string, - systemInstruction?: string + systemInstruction?: string, + signal?: AbortSignal ): Promise<{ text: string, record: UsageRecord }> { const request: GenerateContentParameters = { model: modelName, @@ -56,14 +139,23 @@ export class GeminiAdapter { }; } - const response = await this.client.models.generateContent(request); - const usage = this.extractUsage(response); - const cost = GeminiAdapter.calculateCost(modelName, usage); + try { + const response = await this.withRetry( + () => (this.client.models as any).generateContent(request, { signal }) as Promise, + signal + ); + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage); - return { - text: response.candidates?.[0]?.content?.parts?.[0]?.text || '', - record: { usage, cost } - }; + return { + text: this.extractResultText(response), + record: { usage, cost } + }; + } catch (error) { + if (signal?.aborted) throw error; + console.error(`[GEMINI ADAPTER] generateText failed:`, error); + throw error; + } } /** @@ -73,11 +165,36 @@ export class GeminiAdapter { modelName: string, userPrompt: string, schema: any, - systemInstruction?: string + systemInstruction?: string, + signal?: AbortSignal, + imagePaths?: string[], + options?: { includeThinking?: boolean } ): Promise<{ data: T, record: UsageRecord }> { + const parts: any[] = [] + + // Add image parts if provided + const validImagePaths: string[] = [] + if (imagePaths && imagePaths.length > 0) { + for (const imgPath of imagePaths) { + if (fs.existsSync(imgPath)) { + const data = fs.readFileSync(imgPath).toString('base64') + parts.push({ + inlineData: { + data, + mimeType: 'image/jpeg' + } + }) + validImagePaths.push(imgPath) + } + } + } + + // Add text part LAST + parts.push({ text: userPrompt }) + const request: GenerateContentParameters = { model: modelName, - contents: [{ role: 'user', parts: [{ text: userPrompt }] }], + contents: [{ role: 'user', parts }], config: { responseMimeType: 'application/json', responseSchema: schema @@ -88,36 +205,30 @@ export class GeminiAdapter { request.config!.systemInstruction = systemInstruction; } - const totalUsage: Usage = { promptTokens: 0, candidatesTokens: 0, thinkingTokens: 0, totalTokens: 0 }; - let totalCost = 0; - const MAX_RETRIES = 3; - - for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { - const response = await this.client.models.generateContent(request); - const usage = this.extractUsage(response); - const cost = GeminiAdapter.calculateCost(modelName, usage); + if (options?.includeThinking) { + (request.config as any).thinkingConfig = { + includeThoughts: true, + thinkingBudget: 8000 + }; + } - // Accumulate usage and cost - totalUsage.promptTokens += usage.promptTokens; - totalUsage.candidatesTokens += usage.candidatesTokens; - totalUsage.thinkingTokens = (totalUsage.thinkingTokens || 0) + (usage.thinkingTokens || 0); - totalUsage.totalTokens += usage.totalTokens; - totalCost += cost; + const response = await this.withRetry( + () => (this.client.models as any).generateContent(request, { signal }) as Promise, + signal + ); - const text = response.candidates?.[0]?.content?.parts?.[0]?.text || ''; + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage, 0, validImagePaths.length); + const text = this.extractResultText(response); - try { - return { - data: JSON.parse(text) as T, - record: { usage: totalUsage, cost: totalCost } - }; - } catch (error) { - console.error(`Attempt ${attempt} - Failed to parse Gemini structured response:`, text); - if (attempt === MAX_RETRIES) { - throw new Error('Invalid JSON response from Gemini after multiple attempts'); - } - // Optional: add a small delay or log the retry - } + try { + return { + data: JSON.parse(text) as T, + record: { usage, cost } + }; + } catch (parseError) { + console.error(`[GEMINI ADAPTER] Failed to parse structured response:`, text); + throw parseError; } throw new Error('Unexpected fallthrough in generateStructuredText'); @@ -147,13 +258,16 @@ export class GeminiAdapter { const sanitizedName = sanitizeFilename(rawName); try { - const response = await this.client.files.upload({ - file: uploadPath, - config: { - mimeType, - displayName: sanitizedName - } - }); + const response = await this.withRetry( + () => this.client.files.upload({ + file: uploadPath, + config: { + mimeType, + displayName: sanitizedName + } + }), + undefined // upload doesn't support signal directly in our usage context here, but we could add it if needed + ); return response.uri || ''; } finally { if (isTemp && fs.existsSync(uploadPath)) { @@ -171,7 +285,8 @@ export class GeminiAdapter { userPrompt: string, fileUris: string[], systemInstruction?: string, - audioDuration: number = 0 + audioDuration: number = 0, + signal?: AbortSignal ): Promise<{ text: string, record: UsageRecord }> { const contents = [ { @@ -194,14 +309,23 @@ export class GeminiAdapter { }; } - const response = await this.client.models.generateContent(request); - const usage = this.extractUsage(response); - const cost = GeminiAdapter.calculateCost(modelName, usage, audioDuration); + try { + const response = await this.withRetry( + () => (this.client.models as any).generateContent(request, { signal }) as Promise, + signal + ); + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage, audioDuration); - return { - text: response.candidates?.[0]?.content?.parts?.[0]?.text || '', - record: { usage, cost } - }; + return { + text: this.extractResultText(response), + record: { usage, cost } + }; + } catch (error) { + if (signal?.aborted) throw error; + console.error(`[GEMINI ADAPTER] generateTextFromFiles failed:`, error); + throw error; + } } /** @@ -213,7 +337,8 @@ export class GeminiAdapter { fileUris: string[], schema: any, systemInstruction?: string, - audioDuration: number = 0 + audioDuration: number = 0, + signal?: AbortSignal ): Promise<{ data: T, record: UsageRecord }> { const contents = [ { @@ -238,35 +363,23 @@ export class GeminiAdapter { request.config!.systemInstruction = systemInstruction; } - const totalUsage: Usage = { promptTokens: 0, candidatesTokens: 0, thinkingTokens: 0, totalTokens: 0 }; - let totalCost = 0; - const MAX_RETRIES = 3; - - for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { - const response = await this.client.models.generateContent(request); - const usage = this.extractUsage(response); - const cost = GeminiAdapter.calculateCost(modelName, usage, audioDuration); - - // Accumulate usage and cost - totalUsage.promptTokens += usage.promptTokens; - totalUsage.candidatesTokens += usage.candidatesTokens; - totalUsage.thinkingTokens = (totalUsage.thinkingTokens || 0) + (usage.thinkingTokens || 0); - totalUsage.totalTokens += usage.totalTokens; - totalCost += cost; + const response = await this.withRetry( + () => (this.client.models as any).generateContent(request, { signal }) as Promise, + signal + ); - const text = response.candidates?.[0]?.content?.parts?.[0]?.text || ''; + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage, audioDuration); + const text = this.extractResultText(response); - try { - return { - data: JSON.parse(text) as T, - record: { usage: totalUsage, cost: totalCost } - }; - } catch (error) { - console.error(`Attempt ${attempt} - Failed to parse Gemini structured response:`, text); - if (attempt === MAX_RETRIES) { - throw new Error('Invalid JSON response from Gemini after multiple attempts'); - } - } + try { + return { + data: JSON.parse(text) as T, + record: { usage, cost } + }; + } catch (parseError) { + console.error(`[GEMINI ADAPTER] Failed to parse structured response:`, text); + throw parseError; } throw new Error('Unexpected fallthrough in generateStructuredFromFiles'); @@ -278,7 +391,8 @@ export class GeminiAdapter { async generateDescriptionFromImage( modelName: string, prompt: string, - imageUri: string + imageUri: string, + signal?: AbortSignal ): Promise<{ text: string, record: UsageRecord }> { const contents = [ { @@ -295,21 +409,295 @@ export class GeminiAdapter { contents }; - const response = await this.client.models.generateContent(request); - const usage = this.extractUsage(response); - const cost = GeminiAdapter.calculateCost(modelName, usage); + try { + const response = await this.withRetry( + () => (this.client.models as any).generateContent(request, { signal }) as Promise, + signal + ); + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage, 0, 1); - return { - text: response.candidates?.[0]?.content?.parts?.[0]?.text || '', - record: { usage, cost } + return { + text: this.extractResultText(response), + record: { usage, cost } + }; + } catch (error) { + if (signal?.aborted) throw error; + console.error(`[GEMINI ADAPTER] generateDescriptionFromImage failed:`, error); + throw error; + } + } + + /** + * Generates a structured result (JSON) from Gemini based on multiple images and a prompt. + */ + async generateStructuredFromImages( + modelName: string, + userPrompt: string, + imageUris: string[], + schema: any, + signal?: AbortSignal, + options?: { includeThinking?: boolean } + ): Promise<{ data: T, record: UsageRecord }> { + const parts: any[] = imageUris.map(uri => ({ + fileData: { fileUri: uri, mimeType: 'image/jpeg' } + })); + parts.push({ text: userPrompt }); + + const contents = [{ role: 'user', parts }]; + + const request: GenerateContentParameters = { + model: modelName, + contents, + config: { + responseMimeType: 'application/json', + responseSchema: schema + } }; + + const response = await this.withRetry( + () => (this.client.models as any).generateContent(request, { signal }) as Promise, + signal + ); + + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage, 0, imageUris.length); + const text = this.extractResultText(response); + + try { + return { + data: JSON.parse(text) as T, + record: { usage, cost } + }; + } catch (parseError) { + console.error(`[GEMINI ADAPTER] Failed to parse structured response:`, text); + throw parseError; + } + + throw new Error('Unexpected fallthrough in generateStructuredFromImages'); + } + + /** + * MOCK: Generates an image based on a prompt. + * In a real scenario, this would call the Imagen/Gemini V6 API. + */ + async generateImage( + modelName: string, + prompt: string, + outputPath: string, + imagePaths: string[] = [], + systemInstruction?: string, + signal?: AbortSignal, + options?: { includeThinking?: boolean } + ): Promise<{ path: string, text?: string, record: UsageRecord }> { + try { + // Prepare parts: Image parts FIRST, then text prompt + const parts: any[] = [] + + for (const imgPath of imagePaths) { + if (fs.existsSync(imgPath)) { + const data = fs.readFileSync(imgPath).toString('base64') + parts.push({ + inlineData: { + data, + mimeType: 'image/jpeg' + } + }) + } + } + + parts.push({ text: prompt }) + + // For gemini-3.1-flash-image-preview, we use generateContent + const requestConfig: any = systemInstruction ? { systemInstruction: systemInstruction } : {}; + + if (options?.includeThinking) { + requestConfig.thinkingConfig = { + includeThoughts: true, + thinkingBudget: 8000 + }; + } + + const response = await this.withRetry( + () => (this.client.models as any).generateContent({ + model: modelName, + contents: [{ role: 'user', parts }], + config: requestConfig + }, { signal }) as Promise, + signal + ); + + if (!response.candidates || response.candidates.length === 0) { + throw new Error('No candidates returned from the model.'); + } + + // Extract the image from candidates (inlineData part) and text part + let base64Data: string | undefined; + let modelText: string | undefined; + + for (const part of response.candidates[0].content?.parts || []) { + if (part.inlineData) { + base64Data = part.inlineData.data; + } else if (part.text && !part.thought) { + modelText = part.text; + } + } + + if (!base64Data) { + const candidate = response.candidates[0]; + const finishReason = candidate.finishReason || 'UNKNOWN'; + const text = this.extractResultText(response); + + if (text) { + throw new Error(text); + } + + throw new Error(`Image Model did not generate image. Reason: ${finishReason}, Prompt: ${prompt}`); + } + + const buffer = Buffer.from(base64Data, 'base64'); + + // Ensure directory exists + const dir = path.dirname(outputPath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + + // Save to specified path + fs.writeFileSync(outputPath, buffer); + + // Calculate cost + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage); + + return { + path: outputPath, + text: modelText, + record: { usage, cost } + }; + } catch (error: any) { + if (signal?.aborted) throw error; + console.error('Gemini image generation failed:', error); + // ... existing error parsing logic ... + let message = 'Image generation failed.'; + + // Try to extract a clean message from common error formats + let rawError = error.message; + if (typeof rawError === 'string' && rawError.startsWith('{')) { + try { + const parsed = JSON.parse(rawError); + if (parsed.error && parsed.error.message) { + rawError = parsed.error.message; + } else if (parsed.message) { + rawError = parsed.message; + } + } catch (e) { /* ignore parse error */ } + } + + if (error.status === 'NOT_FOUND' || (rawError && rawError.toLowerCase().includes('not found'))) { + message = `Model '${modelName}' not found. Please verify your Imagen model settings. Note: 'gemini-3.1-flash-image-preview' is recommended for Gemini 3.`; + } else if (rawError) { + message = `Gemini Error: ${rawError}`; + } + + throw new Error(message); + } + } + + /** + * Upscales an image using the Imagen Upscale API. + */ + async upscaleImage( + modelName: string, + inputPath: string, + upscaleFactor: UpscaleFactor, + outputPath: string, + signal?: AbortSignal + ): Promise<{ path: string, record: UsageRecord }> { + try { + if (!fs.existsSync(inputPath)) { + throw new Error(`Input image file not found: ${inputPath}`); + } + + const data = fs.readFileSync(inputPath).toString('base64'); + const mimeType = 'image/jpeg'; // Default for our snapshots + + // Creative Upscaling using generateContent (AI Studio compatible) + // This uses the existing image as a reference and reconstructs it at higher resolution. + const response = await this.withRetry( + () => (this.client.models as any).generateContent({ + model: modelName, + contents: [ + { + role: 'user', + parts: [ + { text: "Regenerate this image at high resolution, preserving all details, textures, and composition exactly as shown. Output the result as an image." }, + { inlineData: { data, mimeType } } + ] + } + ], + config: { + responseModalities: ['IMAGE'], + imageConfig: { + imageSize: upscaleFactor === 'x2' ? '2K' : '4K' + } + } + }, { signal }) as Promise, + signal + ); + + if (!response.candidates || response.candidates.length === 0) { + throw new Error('No candidates returned from the model.'); + } + + // Extract the image from candidates (inlineData part) + let base64Data: string | undefined; + for (const part of response.candidates[0].content?.parts || []) { + if (part.inlineData) { + base64Data = part.inlineData.data; + break; + } + } + + if (!base64Data) { + const candidate = response.candidates[0]; + const finishReason = candidate.finishReason || 'UNKNOWN'; + throw new Error(`Image Model did not generate high-res image. Reason: ${finishReason}`); + } + + const buffer = Buffer.from(base64Data, 'base64'); + + // Ensure directory exists + const dir = path.dirname(outputPath); + if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); + + // Save to specified path + fs.writeFileSync(outputPath, buffer); + + // Calculate cost - using 1 image count since it's an image generation task + const usage = this.extractUsage(response); + const cost = GeminiAdapter.calculateCost(modelName, usage, 0, 1); + + return { + path: outputPath, + record: { usage, cost } + }; + } catch (error: any) { + if (signal?.aborted) throw error; + console.error('Gemini creative upscaling failed:', error); + // Wrap and re-throw with clearer message + let message = error.message || 'Creative upscaling failed.'; + if (message.includes('supported by the Vertex AI')) { + message = 'The native upscaling method is Vertex-only. Retrying with Creative Re-rendering... (Something went wrong with the fallback)'; + } + throw new Error(message); + } } /** * Calculates the cost of a request based on usage and model. * @param audioDuration Duration of audio in seconds if multimodal call. + * @param imageCount Number of images if multimodal call. */ - static calculateCost(model: string, usage: Usage, audioDuration: number = 0): number { + static calculateCost(model: string, usage: Usage, audioDuration: number = 0, imageCount: number = 0): number { const modelSettings = settingsManager.getModelSettings(); const pricing = modelSettings.pricing[model]; if (!pricing) return 0; @@ -346,6 +734,11 @@ export class GeminiAdapter { // Output price applies to both candidates and thinking tokens const totalOutputTokens = usage.candidatesTokens + (usage.thinkingTokens || 0); outputCost = (totalOutputTokens / 1000000) * pricing.output.standard; + + // Handle per-image cost if applicable + if (pricing.output.image) { + outputCost += pricing.output.image * Math.max(1, imageCount); + } } return inputCost + outputCost; diff --git a/src/main/gemini/utils.ts b/src/main/gemini/utils.ts index c3fb8fc..d8414d6 100644 --- a/src/main/gemini/utils.ts +++ b/src/main/gemini/utils.ts @@ -8,32 +8,33 @@ export interface TranscriptItem { text: string } -const TRANSCRIPT_PROMPT = `Extract a detailed transcript from the provided audio in SRT (Subtitle) format. - -Each entry must strictly follow this format: -"""str -1 -00:00:01,000 --> 00:00:05,000 -Text content here. - -2 -00:00:05,000 --> 00:00:10,000 -[Music playing] -""" +const TRANSCRIPT_PROMPT = `Extract a detailed transcript from the provided audio file. + +Each entry must be on a SINGLE LINE and strictly follow this format: +from-timestamp , to-timestamp - Caption segment text + +Example: +00:00:01,000 , 00:00:05,000 - Text content here. +00:00:05,000 , 00:00:10,000 - [Music playing] Rules: +- Respond ONLY with the transcript content. +- Each line represents exactly one segment. - Use HH:MM:SS,mmm format for timestamps. -- Respond ONLY with the SRT content. +- Use a single comma (with spaces around it) to separate 'from' and 'to' timestamps. +- Use a single hyphen (with spaces around it) to separate the timestamps from the text. - Do not include any preamble, conversational text, or markdown code blocks. - **IMPORTANT**: Transcribe significant audio events (e.g., [Music], [Applause], [Laughter], [Silence]) in brackets.` -const TRANSCRIPT_CORRECTION_PROMPT = `You are an expert transcriber. I am providing you with an audio file and an initial transcript (in SRT format) that was generated for it. +const TRANSCRIPT_CORRECTION_PROMPT = `You are an expert transcriber. I am providing you with an audio file and an initial transcript (in custom line-based format) that was generated for it. Your task is to review the transcript against the audio and correct any errors (mishearings, missing words, incorrect timestamps). Rules: -1. Respond ONLY with the FULL corrected transcript in SRT format. -2. Do not include any preamble, conversational text, or markdown code blocks. -3. **CRITICAL**: Preserve all audio event markers (e.g., [Music], [Applause]) unless they are clearly incorrect.` +1. Respond ONLY with the FULL corrected transcript in the same format: +from-timestamp , to-timestamp - Caption segment text +2. Each entry must be on its own line. +3. Do not include any preamble, conversational text, or markdown code blocks. +4. **CRITICAL**: Preserve all audio event markers (e.g., [Music], [Applause]) unless they are clearly incorrect.` /** * Normalizes a timestamp string to HH:MM:SS,mmm format. @@ -60,52 +61,39 @@ function normalizeTimestamp(t: string): string { } /** - * Parses SRT text into TranscriptItem array. + * Parses custom line-based transcript text into TranscriptItem array. + * Format: from-timestamp , to-timestamp - Caption segment text */ -export function parseSRT(srt: string): TranscriptItem[] { +export function parseTranscript(text: string): TranscriptItem[] { const items: TranscriptItem[] = [] - let cleanSrt = srt.replace(/```[a-z]*\n?/gi, '').replace(/```/g, '').trim() - const allLines = cleanSrt.split(/\r?\n/).map(l => l.trim()) - const timestampRegex = /((?:\d{1,2}:)?\d{1,2}:\d{2}(?:[.,]\d{1,3})?)\s*-->\s*((?:\d{1,2}:)?\d{1,2}:\d{2}(?:[.,]\d{1,3})?)/ - - for (let i = 0; i < allLines.length; i++) { - const line = allLines[i] - if (/^\d+$/.test(line)) { - const nextLine = allLines[i + 1] - if (nextLine && timestampRegex.test(nextLine)) { - const timeMatch = nextLine.match(timestampRegex)! - const start = normalizeTimestamp(timeMatch[1]) - const end = normalizeTimestamp(timeMatch[2]) - - let textLines: string[] = [] - let j = i + 2 - while (j < allLines.length) { - const cur = allLines[j] - const ahead = allLines[j + 1] - if (/^\d+$/.test(cur) && ahead && timestampRegex.test(ahead)) { - break - } - if (cur !== '' && cur !== '\t') { - textLines.push(cur) - } - j++ - } - items.push({ start, end, text: textLines.join(' ') }) - i = j - 1 - } + let cleanText = text.replace(/```[a-z]*\n?/gi, '').replace(/```/g, '').trim() + const allLines = cleanText.split(/\r?\n/).map(l => l.trim()).filter(l => l !== '') + + // Regex to match: TIMESTAMP , TIMESTAMP - TEXT + // Groups: 1=start, 2=end, 3=text + const lineRegex = /^((?:\d{1,2}:)?\d{1,2}:\d{2}(?:[.,]\d{1,3})?)\s*,\s*((?:\d{1,2}:)?\d{1,2}:\d{2}(?:[.,]\d{1,3})?)\s*-\s*(.*)$/ + + for (const line of allLines) { + const match = line.match(lineRegex) + if (match) { + const start = normalizeTimestamp(match[1]) + const end = normalizeTimestamp(match[2]) + const segmentText = match[3].trim() + items.push({ start, end, text: segmentText }) } } return items } /** - * Generates standard SRT text from TranscriptItem array. + * Generates custom line-based transcript text from TranscriptItem array. + * Format: from-timestamp , to-timestamp - Caption segment text */ -export function generateSRT(items: TranscriptItem[]): string { - return items.map((item, index) => { +export function formatTranscript(items: TranscriptItem[]): string { + return items.map((item) => { const start = normalizeTimestamp(item.start) const end = normalizeTimestamp(item.end) - return `${index + 1}\n${start} --> ${end}\n${item.text}\n` + return `${start} , ${end} - ${item.text}` }).join('\n') } @@ -115,22 +103,23 @@ export function generateSRT(items: TranscriptItem[]): string { export async function extractTranscript( audioPath: string, audioDuration: number = 0, - rawSrt?: string -): Promise<{ items: TranscriptItem[], record: UsageRecord }> { + rawTranscriptText?: string, + signal?: AbortSignal +): Promise<{ items: TranscriptItem[], rawResponseText: string, record: UsageRecord }> { const adapter = GeminiAdapter.create() const modelSettings = settingsManager.getModelSettings() - const modelName = rawSrt + const modelName = rawTranscriptText ? modelSettings.selection['corrected-transcript'] : modelSettings.selection['raw-transcript'] const fileUri = await adapter.uploadFile(audioPath, 'audio/mpeg') - const userPrompt = rawSrt - ? `Initial Transcript:\n${rawSrt}` - : 'Generate the SRT for this audio.' + const userPrompt = rawTranscriptText + ? `Initial Transcript:\n${rawTranscriptText}` + : 'Generate the transcript for this audio.' - const systemInstruction = rawSrt + const systemInstruction = rawTranscriptText ? TRANSCRIPT_CORRECTION_PROMPT : TRANSCRIPT_PROMPT @@ -139,11 +128,13 @@ export async function extractTranscript( userPrompt, [fileUri], systemInstruction, - audioDuration + audioDuration, + signal ) return { - items: parseSRT(text), + items: parseTranscript(text), + rawResponseText: text, record } } diff --git a/src/main/index.ts b/src/main/index.ts index 402b541..da0a978 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -9,11 +9,23 @@ import { threadManager } from './threads' import * as extraction from './pipeline/phases/extraction' import * as generation from './pipeline/phases/generation' import * as intent from './pipeline/phases/intent' +import * as supply from './pipeline/phases/supply' import * as assembly from './pipeline/phases/assembly' -import { checkFFmpegAvailability } from './ffmpeg' +import * as thumbnail from './pipeline/phases/thumbnail' + +import * as imageIntent from './pipeline/phases/image-intent' +import * as imageGeneration from './pipeline/phases/image-generation' +import { backgroundTaskManager } from './tasks' +import { GeminiAdapter } from './gemini/adapter' + +import { checkFFmpegAvailability, getVideoMetadata } from './ffmpeg' import { checkScenedetectAvailability } from './scenedetect' +import { checkYtDlpAvailability, downloadVideo, getVideoFormats } from './ytdlp' +import { THREAD_DIRS } from './constants/paths' import { electronApp, optimizer, is } from '@electron-toolkit/utils' +const activePipelines = new Map() + protocol.registerSchemesAsPrivileged([ { scheme: 'media', @@ -57,7 +69,15 @@ function createWindow(): void { } app.whenReady().then(() => { - electronApp.setAppUserModelId('com.electron') + // Set correct appId before any path resolution if possible, + // though getPath might already have been called/cached. + electronApp.setAppUserModelId('com.frameflow.app') + + // Initialize managers that depend on app paths + settingsManager.init() + threadManager.init() + + backgroundTaskManager.init() app.on('browser-window-created', (_, window) => { optimizer.watchWindowShortcuts(window) @@ -102,47 +122,136 @@ app.whenReady().then(() => { } }) + ipcMain.handle('show-open-dialog', async (_event, options) => { + return await dialog.showOpenDialog(options) + }) + ipcMain.handle('check-system-requirements', async () => { - const [ffmpegAvailable, scenedetectAvailable] = await Promise.all([ + const [ffmpegAvailable, scenedetectAvailable, ytDlpAvailable] = await Promise.all([ checkFFmpegAvailability(), - checkScenedetectAvailability() + checkScenedetectAvailability(), + checkYtDlpAvailability() ]) - return { ffmpegAvailable, scenedetectAvailable } + return { + ffmpegAvailable, + scenedetectAvailable, + ytDlpAvailable, + isTempDirUnsafe: settingsManager.isTempDirUnsafe() + } + }) + + ipcMain.handle('get-video-metadata', async (_event, filePath: string) => { + return await getVideoMetadata(filePath) + }) + + ipcMain.handle('fetch-video-formats', async (_event, url: string) => { + return await getVideoFormats(url) + }) + + ipcMain.handle('download-video', async (event, url: string, resolution?: string) => { + const tempDir = join(settingsManager.getTempDir(), `download-${Date.now()}`) + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }) + } + return await downloadVideo(url, tempDir, resolution, (percent) => { + event.sender.send('download-progress', percent) + }) + }) + + ipcMain.handle('is-temp-dir-unsafe', () => { + return settingsManager.isTempDirUnsafe() + }) + + ipcMain.handle('debug-log', (_event, ...args: any[]) => { + console.log('[FRONTEND LOG]', ...args) }) ipcMain.handle('start-pipeline', async (event, { threadId, newAiMessageId }) => { + console.log(`\n===============\n[DEBUG IPC] start-pipeline called: threadId=${threadId}, newAiMessageId=${newAiMessageId}`) const window = BrowserWindow.fromWebContents(event.sender) - if (!window) return + if (!window) { + console.log(`[DEBUG IPC] FAILED: window not found`) + return + } const thread = threadManager.getThread(threadId) - if (!thread) return + if (!thread) { + console.log(`[DEBUG IPC] FAILED: thread not found`) + return + } - // Derive edit reference from the last user message - const lastUserMsg = threadManager.getLatestUserMessage(threadId) - const editRefId = lastUserMsg?.editRefId + // The newly created AI pending message links back to the user prompt via editRefId + const aiMsg = thread.messages.find(m => m.id === newAiMessageId) + const userMsgId = aiMsg?.editRefId + console.log(`[DEBUG IPC] aiMsg found? ${!!aiMsg}, userMsgId=${userMsgId}`) - // Prepare the context - // Full thread history is the context for the pipeline - const context = threadManager.getThreadContext(threadId) + // Traverse graph lineage backwards + const { text: context, attachedImages } = threadManager.getBranchContext(threadId, userMsgId) // Prepare the base timeline - const baseTimeline = editRefId ? thread.messages.find(m => m.id === editRefId)?.timeline : undefined; + const baseTimeline = userMsgId ? thread.messages.find(m => m.id === userMsgId)?.timeline : undefined; - const pipeline = new Pipeline(window, newAiMessageId, threadId, context, baseTimeline, editRefId) + const pipeline = new Pipeline(window, newAiMessageId, threadId, context, baseTimeline, userMsgId, attachedImages) - pipeline - .register(extraction.ensureLowResolution, { skipIf: ctx => !!ctx.preprocessing.lowResVideoPath }) - .register(extraction.convertToAudio, { skipIf: ctx => !!ctx.preprocessing.audioPath }) - .register(extraction.extractRawTranscript, { skipIf: ctx => !!ctx.preprocessing.rawTranscriptPath }) - .register(intent.determineIntent) - // These steps only run if intent is generate-timeline (handled by pipeline logic if needed, but here we can add skipIf or the determineIntent can just finish) - .register(extraction.extractCorrectedTranscript, { skipIf: ctx => !!ctx.preprocessing.correctedTranscriptPath }) - .register(extraction.extractSceneTiming, { skipIf: ctx => ctx.intentResult?.type === 'text' || !!ctx.preprocessing.sceneTimesPath }) - .register(extraction.generateSceneDescription, { skipIf: ctx => ctx.intentResult?.type === 'text' || !!ctx.preprocessing.sceneDescriptionsPath }) - .register(generation.buildShorterTimeline, { skipIf: ctx => ctx.intentResult?.type === 'text' }) - .register(assembly.assembleVideoFromTimeline, { skipIf: ctx => ctx.intentResult?.type === 'text' }) + // Ensure background processing is running (will resume/retry if tasks are missing/failed) + if (thread.type === 'image') { + backgroundTaskManager.startImageProcessing(threadId) + } else { + backgroundTaskManager.startPreprocessing(threadId) + } + + if (thread.type === 'image') { + pipeline + .register(async (data, ctx) => { + await ctx.updateStatus('Waiting for image analysis...') + await ctx.waitForTask('imageExtraction') + ctx.next(data) + }) + .register(imageIntent.determineImageIntent) + .register(supply.supplyController, { skipIf: ctx => ctx.intentResult?.type !== 'generate-image' }) + .register(imageGeneration.generateOutputImage, { skipIf: ctx => ctx.intentResult?.type !== 'generate-image' }) + } else { + pipeline + .register(extraction.waitForEnsureLowResolution) + .register(extraction.waitForConvertToAudio) + .register(extraction.waitForExtractRawTranscript) + .register(extraction.waitForExtractCorrectedTranscript) + .register(extraction.waitForExtractSceneTiming) + .register(extraction.waitForGenerateSceneDescription) + .register(intent.determineIntent) + .register(supply.supplyController, { skipIf: ctx => ctx.intentResult?.type === 'text' }) + // These steps only run if intent is generate-timeline + .register(generation.waitForEnrichTranscript, { skipIf: ctx => ctx.intentResult?.type === 'text' || ctx.intentResult?.type === 'generate-thumbnail' }) + .register(generation.buildShorterTimeline, { skipIf: ctx => ctx.intentResult?.type === 'text' || ctx.intentResult?.type === 'generate-thumbnail' }) + .register(thumbnail.generateThumbnail, { skipIf: ctx => ctx.intentResult?.type !== 'generate-thumbnail' }) + .register(assembly.assembleVideoFromTimeline, { skipIf: ctx => ctx.intentResult?.type === 'text' || ctx.intentResult?.type === 'generate-thumbnail' }) + } - await pipeline.start({}) + console.log(`[DEBUG IPC] pipeline configured. Calling pipeline.start() in background...`) + activePipelines.set(newAiMessageId, pipeline) + + pipeline.start({}) + .then(() => { + console.log(`[DEBUG IPC] pipeline.start() completed successfully!`) + }) + .catch((e) => { + console.error(`[DEBUG IPC] pipeline.start() threw error:`, e) + }) + .finally(() => { + activePipelines.delete(newAiMessageId) + }) + + return true + }) + + ipcMain.handle('abort-pipeline', async (_event, messageId) => { + console.log(`[DEBUG IPC] abort-pipeline called for messageId=${messageId}`) + const pipeline = activePipelines.get(messageId) + if (pipeline) { + pipeline.abort() + return true + } + return false }) ipcMain.handle('get-temp-dir', () => { @@ -158,13 +267,22 @@ app.whenReady().then(() => { return null } - const newPath = result.filePaths[0] + const selectedPath = result.filePaths[0] + const newPath = join(selectedPath, 'FrameFlow') + + if (!fs.existsSync(newPath)) { + fs.mkdirSync(newPath, { recursive: true }) + } + settingsManager.setTempDir(newPath) + threadManager.syncWithArtifactDir() return newPath }) ipcMain.handle('reset-temp-dir', () => { - return settingsManager.resetTempDir() + const path = settingsManager.resetTempDir() + threadManager.syncWithArtifactDir() + return path }) ipcMain.handle('open-temp-dir', async () => { @@ -193,8 +311,19 @@ app.whenReady().then(() => { }) // Thread Management - ipcMain.handle('create-thread', async (_event, { videoPath, videoName }) => { - return threadManager.createThread(videoPath, videoName) + ipcMain.handle('create-thread', async (_event, { videoPath, videoName, imagePaths }) => { + const newThread = await threadManager.createThread(videoPath, videoName, imagePaths) + if (newThread.type === 'image') { + backgroundTaskManager.startImageProcessing(newThread.id) + } else { + backgroundTaskManager.startPreprocessing(newThread.id) + } + return newThread + }) + + ipcMain.handle('retry-preprocessing', async (_event, threadId) => { + await backgroundTaskManager.startPreprocessing(threadId) + return true }) ipcMain.handle('get-all-threads', () => { @@ -222,12 +351,16 @@ app.whenReady().then(() => { return false }) - ipcMain.handle('add-message', (_event, { threadId, message }) => { - return threadManager.addMessageToThread(threadId, message) + ipcMain.handle('add-message', async (_event, { threadId, message }) => { + return await threadManager.addMessageToThread(threadId, message) }) - ipcMain.handle('remove-message', (_event, { threadId, messageId }) => { - return threadManager.removeMessageFromThread(threadId, messageId) + ipcMain.handle('remove-message', async (_event, { threadId, messageId }) => { + return await threadManager.removeMessageBranchFromThread(threadId, messageId) + }) + + ipcMain.handle('save-node-positions', async (_event, { threadId, positions }) => { + return await threadManager.updateThreadNodePositions(threadId, positions) }) ipcMain.handle('show-confirmation', async (_event, { title, message, detail, type = 'question', buttons = ['Cancel', 'Yes'], defaultId = 1, cancelId = 0 }) => { @@ -269,6 +402,49 @@ app.whenReady().then(() => { } }) + ipcMain.handle('upscale-image', async (_event, { threadId, messageId, imagePath, upscaleFactor }) => { + console.log(`[DEBUG IPC] upscale-image called: threadId=${threadId}, messageId=${messageId}, upscaleFactor=${upscaleFactor}`) + const adapter = GeminiAdapter.create() + const thread = threadManager.getThread(threadId) + if (!thread) throw new Error('Thread not found') + + const filename = basename(imagePath, extname(imagePath)) + const outputFilename = `${filename}_upscale_${upscaleFactor.replace('x', '')}x_${Date.now()}.png` + const outputPath = join(thread.tempDir, THREAD_DIRS.GENERATED_IMAGES, outputFilename) + + const modelSettings = settingsManager.getModelSettings() + const modelName = modelSettings.selection['image-upscale'] + + const result = await adapter.upscaleImage(modelName, imagePath, upscaleFactor as any, outputPath) + + // Update message metadata for persistence + const message = thread.messages.find((m) => m.id === messageId) + if (message && message.files) { + const actualFile = message.files.find((f) => f.type === 'actual') + if (actualFile) { + console.log(`[DEBUG] Updating message ${messageId} with upscale factor ${upscaleFactor}`) + if (upscaleFactor === 'x2') actualFile.upscale2k = result.path + if (upscaleFactor === 'x4') actualFile.upscale4k = result.path + + await threadManager.updateMessageInThread(threadId, messageId, { + files: message.files + }) + + // Track usage and cost + if (result.record) { + console.log(`[DEBUG] Tracking upscale usage for ${messageId}, cost: ${result.record.cost}`) + await threadManager.updateMessageUsage(threadId, messageId, result.record) + } + } else { + console.warn(`[DEBUG] No actual file found for message ${messageId}`) + } + } else { + console.warn(`[DEBUG] Message ${messageId} not found or has no files. Looking for ID: ${messageId}`) + } + + return result.path + }) + app.on('activate', function () { if (BrowserWindow.getAllWindows().length === 0) createWindow() }) diff --git a/src/main/pipeline/index.ts b/src/main/pipeline/index.ts index dcefcfa..98035e6 100644 --- a/src/main/pipeline/index.ts +++ b/src/main/pipeline/index.ts @@ -1,27 +1,33 @@ import { BrowserWindow } from 'electron' -import { FileType, Thread, Message } from '../../shared/types' +import { FileType, Thread, Message, EnrichedTimelineSegment } from '../../shared/types' export type PipelineFunction = (data: any, context: PipelineContext) => Promise | void; import { threadManager } from '../threads' export interface PipelineContext { - updateStatus: (status: string) => void; + updateStatus: (status: string) => Promise; next: (data: any) => void; - finish: (message: string, video?: { path: string; type: FileType.Preview | FileType.Actual }, timeline?: any, options?: { version?: number; shouldVersion?: boolean }) => void; - savePreprocessing: (updates: Partial) => void; - recordUsage: (record: import('../../shared/types').UsageRecord) => void; + finish: (message: string, video?: { path: string; type: FileType.Preview | FileType.Actual }, timeline?: EnrichedTimelineSegment[], options?: { version?: number; shouldVersion?: boolean, resultType?: 'video' | 'thumbnail' | 'summary' | 'image', files?: Array<{ url: string, type: FileType }> }) => Promise; + fail: (error: string) => Promise; + savePreprocessing: (updates: Partial) => Promise; + recordUsage: (record: import('../../shared/types').UsageRecord) => Promise; + waitForTask: (taskId: string) => Promise; + signal: AbortSignal; threadId: string; videoPath: string; tempDir: string; preprocessing: Thread['preprocessing']; messageId: string; - baseTimeline?: any; // The timeline to based this generation on + baseTimeline?: EnrichedTimelineSegment[]; // The timeline to based this generation on context: string; // Full conversation history as text editRefId?: string; // ID of the message being edited/referenced intentResult?: import('../../shared/types').IntentResult; + attachedImages?: string[]; } +import { backgroundTaskManager } from '../tasks' + export class Pipeline { private steps: { fn: PipelineFunction; options?: { skipIf?: (context: PipelineContext) => boolean } }[] = [] private currentStepIndex = 0 @@ -30,16 +36,20 @@ export class Pipeline { private threadId: string private context: string private editRefId?: string - private baseTimeline?: any + private baseTimeline?: EnrichedTimelineSegment[] private intentResult?: import('../../shared/types').IntentResult + private attachedImages?: string[] + private isFinished: boolean = false + private abortController: AbortController = new AbortController() - constructor(browserWindow: BrowserWindow, messageId: string, threadId: string, context: string, baseTimeline?: any, editRefId?: string) { + constructor(browserWindow: BrowserWindow, messageId: string, threadId: string, context: string, baseTimeline?: EnrichedTimelineSegment[], editRefId?: string, attachedImages?: string[]) { this.browserWindow = browserWindow this.messageId = messageId this.threadId = threadId this.context = context this.baseTimeline = baseTimeline this.editRefId = editRefId + this.attachedImages = attachedImages } register(fn: PipelineFunction, options?: { skipIf?: (context: PipelineContext) => boolean }): this { @@ -47,80 +57,130 @@ export class Pipeline { return this; } - async start(initialData: any): Promise { - this.currentStepIndex = 0; - if (this.steps.length > 0) { - await this.runStep(initialData); + abort(): void { + if (this.isFinished) { + console.log(`[PIPELINE CORE] abort() skipped - already finished for message ${this.messageId}`) + return } + console.log(`[PIPELINE CORE] abort() TRIGGERED for message ${this.messageId}`) + this.abortController.abort() + + // Immediately update UI to reflect stopped state + this.fail('Processing stopped by user'); } - private async runStep(data: any): Promise { - if (this.currentStepIndex >= this.steps.length) { - return; - } + async start(initialData: any): Promise { + console.log(`[PIPELINE CORE] start() called. Total steps registered: ${this.steps.length}`) + this.currentStepIndex = 0; + let currentData = initialData; - const thread = threadManager.getThread(this.threadId) - if (!thread) { - console.error(`Thread ${this.threadId} not found during pipeline execution`) - return - } + try { + while (this.currentStepIndex < this.steps.length && !this.isFinished) { + if (this.abortController.signal.aborted) { + console.log(`[PIPELINE CORE] Pipeline aborted. Breaking loop.`) + break + } + + const thread = threadManager.getThread(this.threadId) + if (!thread) { + throw new Error(`Thread ${this.threadId} not found during pipeline execution`) + } + + const step = this.steps[this.currentStepIndex]; + + // Check skip condition + const skipContext = this.createContext(currentData, thread); + if (step.options?.skipIf && step.options.skipIf(skipContext)) { + console.log(`[PIPELINE CORE] Skipping step index ${this.currentStepIndex}: ${step.fn.name || 'anonymous'}`) + this.currentStepIndex++ + continue + } + + console.log(`[PIPELINE CORE] Executing step index ${this.currentStepIndex}: ${step.fn.name || 'anonymous'}`) + + const prevIndex = this.currentStepIndex; + let nextCalled = false; + + const context = this.createContext(currentData, thread); + // Override next to capture data and advance index + context.next = (data: any) => { + currentData = data; + this.currentStepIndex++; + nextCalled = true; + console.log(`[PIPELINE CONTEXT] next() called. Advancing to index ${this.currentStepIndex}`) + }; + + await step.fn(currentData, context); - const step = this.steps[this.currentStepIndex]; - - // check if we should skip this step - if (step.options?.skipIf) { - const contextForCheck: PipelineContext = { - threadId: this.threadId, - videoPath: thread.videoPath, - tempDir: thread.tempDir, - preprocessing: thread.preprocessing, - messageId: this.messageId, - context: this.context, - baseTimeline: undefined, - updateStatus: () => { }, - next: () => { }, - finish: () => { }, - savePreprocessing: () => { }, - recordUsage: () => { } + // If step finished without calling next, finish, or fail, it's an implicit stop or we should check state + if (!nextCalled && !this.isFinished) { + console.log(`[PIPELINE CORE] Step ${prevIndex} finished without calling next() or finish(). Stopping chain.`) + break; + } + } + console.log(`[PIPELINE CORE] Loop exited. isFinished=${this.isFinished}, aborted=${this.abortController.signal.aborted}`) + + // Final safety: if loop exited due to abort but wasn't marked finished, do it now + if (!this.isFinished && this.abortController.signal.aborted) { + await this.fail('Processing stopped by user'); + } + } catch (e: any) { + if (this.abortController.signal.aborted) { + console.log(`[PIPELINE CORE] Caught expected abort error:`, e?.message) + } else { + console.error(`[PIPELINE CORE] Error in execution loop:`, e) + await this.fail(e instanceof Error ? e.message : String(e)) } } + } + private createContext(data: any, thread: Thread): PipelineContext { const self = this - const context: PipelineContext = { + return { threadId: this.threadId, - videoPath: thread.videoPath, + videoPath: thread.videoPath || '', tempDir: thread.tempDir, preprocessing: thread.preprocessing, messageId: this.messageId, context: this.context, editRefId: this.editRefId, baseTimeline: this.baseTimeline, + attachedImages: this.attachedImages, get intentResult() { return self.intentResult }, set intentResult(val) { self.intentResult = val }, - updateStatus: (status: string) => { - // Send update to UI + signal: this.abortController.signal, + waitForTask: async (taskId: string) => { + console.log(`[PIPELINE CONTEXT] waitForTask('${taskId}') called.`) + await Promise.race([ + backgroundTaskManager.waitForTask(this.threadId, taskId), + new Promise((_, reject) => { + if (this.abortController.signal.aborted) { + reject(new Error('Pipeline aborted')) + } + this.abortController.signal.addEventListener('abort', () => reject(new Error('Pipeline aborted')), { once: true }) + }) + ]); + console.log(`[PIPELINE CONTEXT] waitForTask('${taskId}') RESOLVED.`) + }, + updateStatus: async (status: string) => { + console.log(`[PIPELINE CONTEXT] updateStatus('${status}') called.`) this.browserWindow.webContents.send('pipeline-update', { id: this.messageId, type: 'status', content: status }) - - // Persist to Thread if (this.threadId) { - threadManager.updateMessageInThread(this.threadId, this.messageId, { + await threadManager.updateMessageInThread(this.threadId, this.messageId, { content: status, isPending: true }) } }, - recordUsage: (record: import('../../shared/types').UsageRecord) => { + recordUsage: async (record: import('../../shared/types').UsageRecord) => { if (this.threadId) { - threadManager.updateMessageUsage(this.threadId, this.messageId, record) - - // Send update to UI for real-time cost display + await threadManager.updateMessageUsage(this.threadId, this.messageId, record) const updatedThread = threadManager.getThread(this.threadId) const updatedMessage = updatedThread?.messages.find(m => m.id === this.messageId) - if (updatedMessage) { this.browserWindow.webContents.send('pipeline-update', { id: this.messageId, @@ -131,11 +191,12 @@ export class Pipeline { } } }, - savePreprocessing: (updates: Partial) => { + savePreprocessing: async (updates: Partial) => { + console.log(`[PIPELINE CONTEXT] savePreprocessing() called.`) if (this.threadId) { const currentThread = threadManager.getThread(this.threadId) if (currentThread) { - threadManager.updateThread(this.threadId, { + await threadManager.updateThread(this.threadId, { preprocessing: { ...(currentThread.preprocessing || {}), ...updates @@ -144,60 +205,70 @@ export class Pipeline { } } }, - next: (nextData: any) => { - this.currentStepIndex++ - this.runStep(nextData) + next: (_nextData?: any) => { + // This is a placeholder, will be overridden in the loop }, - finish: (message: string, video?: { path: string; type: FileType.Preview | FileType.Actual }, timeline?: any, options?: { version?: number, shouldVersion?: boolean }) => { - let finalVersion: number | undefined = undefined + finish: async (message: string, video?: { path: string; type: FileType.Preview | FileType.Actual }, timeline?: EnrichedTimelineSegment[], options?: { version?: number, shouldVersion?: boolean, resultType?: 'video' | 'thumbnail' | 'summary' | 'image', files?: Array<{ url: string, type: FileType }> }) => { + return this.finish(message, video, timeline, options); + }, + fail: async (error: string) => { + return this.fail(error); + } + } + } - if (options?.version) { - finalVersion = options.version - } else if (options?.shouldVersion && this.threadId) { - finalVersion = threadManager.getNextVersion(this.threadId) - } + private async finish(message: string, video?: { path: string; type: FileType.Preview | FileType.Actual }, timeline?: EnrichedTimelineSegment[], options?: { version?: number, shouldVersion?: boolean, resultType?: 'video' | 'thumbnail' | 'summary' | 'image', files?: Array<{ url: string, type: FileType }> }) { + console.log(`[PIPELINE CORE] finish() called.`) + if (this.isFinished) return + this.isFinished = true - // Send finish to UI - this.browserWindow.webContents.send('pipeline-update', { - id: this.messageId, - type: 'finish', - content: message, - video, - timeline, - version: finalVersion - }) + const updates: Partial = { + content: message, + files: options?.files || (video ? [{ url: video.path, type: video.type }] : []), + timeline: timeline || [], + resultType: options?.resultType, + isPending: false + } - // Persist to Thread - if (this.threadId) { - const updates: Partial = { - content: message, - isPending: false, - timeline, - version: finalVersion - } + if (options?.shouldVersion !== false) { + updates.version = options?.version || Date.now() + } - if (video) { - updates.files = [{ url: video.path, type: video.type }] - } + this.browserWindow.webContents.send('pipeline-update', { + id: this.messageId, + type: 'finish', + content: message, + files: updates.files, + timeline: updates.timeline, + resultType: updates.resultType, + version: updates.version + }) - threadManager.updateMessageInThread(this.threadId, this.messageId, updates) - } - } + if (this.threadId) { + await threadManager.updateMessageInThread(this.threadId, this.messageId, updates) } + } - // Check skip condition - if (step.options?.skipIf && step.options.skipIf(context)) { - // Skip this step and proceed to next immediately with SAME data - this.currentStepIndex++ - this.runStep(data) - return - } + private async fail(error: string) { + console.log(`[PIPELINE CORE] fail() called: ${error}`) + if (this.isFinished) return + this.isFinished = true - try { - await step.fn(data, context); - } catch (error) { - console.error('Pipeline step failed:', error); - context.updateStatus(`Error: ${error instanceof Error ? error.message : String(error)}`); + const isAborted = this.abortController.signal.aborted + const statusContent = isAborted ? 'Processing stopped by user' : `Error: ${error}` + + // Important: Send ONLY finish here to avoid race conditions in the renderer + this.browserWindow.webContents.send('pipeline-update', { + id: this.messageId, + type: 'finish', + content: statusContent + }) + + if (this.threadId) { + await threadManager.updateMessageInThread(this.threadId, this.messageId, { + content: statusContent, + isPending: false + }) } } } \ No newline at end of file diff --git a/src/main/pipeline/phases/assembly.ts b/src/main/pipeline/phases/assembly.ts index bd7f111..627d590 100644 --- a/src/main/pipeline/phases/assembly.ts +++ b/src/main/pipeline/phases/assembly.ts @@ -1,10 +1,12 @@ -import { FileType } from '../../../shared/types' +import { FileType, EnrichedTimelineSegment } from '../../../shared/types' import { PipelineFunction } from '../index' -import { TimelineSegment } from '../../../shared/types' import { assembleVideo } from '../../ffmpeg' +import fs from 'fs' +import path from 'path' +import { THREAD_DIRS } from '../../constants/paths' export const assembleVideoFromTimeline: PipelineFunction = async (data, context) => { - const timeline = data.timeline as TimelineSegment[] + const timeline = data.timeline as EnrichedTimelineSegment[] const videoPath = context.preprocessing.lowResVideoPath || context.videoPath // Use original high-res video for assembly if (!videoPath) { @@ -20,14 +22,18 @@ export const assembleVideoFromTimeline: PipelineFunction = async (data, context) context.updateStatus('Assembling video from timeline...') try { + const resultsDir = path.join(context.tempDir, THREAD_DIRS.GENERATED_VIDEOS) + if (!fs.existsSync(resultsDir)) fs.mkdirSync(resultsDir, { recursive: true }) + const outputPath = await assembleVideo( videoPath, timeline, - context.tempDir, + resultsDir, context.messageId, (percent) => { context.updateStatus(`Assembling video (${percent}%)...`) - } + }, + context.signal ) context.finish('Processing complete. Your video is ready.', { @@ -35,7 +41,9 @@ export const assembleVideoFromTimeline: PipelineFunction = async (data, context) type: FileType.Preview }, timeline, { shouldVersion: true }) } catch (error) { - console.error('Assembly failed:', error) + if (!context.signal.aborted) { + console.error('Assembly failed:', error) + } context.finish('Video assembly failed.') } } diff --git a/src/main/pipeline/phases/extraction.ts b/src/main/pipeline/phases/extraction.ts index 4f4bb8b..3cc4013 100644 --- a/src/main/pipeline/phases/extraction.ts +++ b/src/main/pipeline/phases/extraction.ts @@ -2,13 +2,14 @@ import { PipelineFunction } from '../index' import * as ffmpegAdapter from '../../ffmpeg' import fs from 'fs' import path from 'path' -import { extractTranscript, generateSRT } from '../../gemini/utils' +import { extractTranscript, formatTranscript } from '../../gemini/utils' import { SceneDetector, checkScenedetectAvailability } from '../../scenedetect' import { GeminiAdapter } from '../../gemini/adapter' import { Scene } from '../../scenedetect/types' import { GEMINI_MODEL_2_5_FLASH_LITE } from '../../constants/gemini' +import { settingsManager } from '../../settings' +import { THREAD_DIRS } from '../../constants/paths' -const BASE_SCENE_PROMPT = `Describe the visual action, setting, and atmosphere of this image in one concise sentence. Focus on what is happening.` export const ensureLowResolution: PipelineFunction = async (_data, context) => { const videoPath = context.videoPath @@ -22,10 +23,12 @@ export const ensureLowResolution: PipelineFunction = async (_data, context) => { } context.updateStatus('Downscaling video to 480p for faster processing...') - const tempDir = context.tempDir - const lowResPath = await ffmpegAdapter.toLowResolution(videoPath, tempDir, (percent) => { + const videoDir = path.join(context.tempDir, THREAD_DIRS.VIDEO) + if (!fs.existsSync(videoDir)) fs.mkdirSync(videoDir, { recursive: true }) + + const lowResPath = await ffmpegAdapter.toLowResolution(videoPath, videoDir, (percent) => { context.updateStatus(`Downscaling video... ${percent}%`) - }) + }, context.signal) context.savePreprocessing({ lowResVideoPath: lowResPath }) context.updateStatus('Video downscaled successfully.') @@ -36,10 +39,12 @@ export const convertToAudio: PipelineFunction = async (data, context) => { const videoPath = context.preprocessing.lowResVideoPath! || context.videoPath; context.updateStatus('Converting video to audio...') - const tempDir = context.tempDir - const audioPath = await ffmpegAdapter.toAudio(videoPath, tempDir, (percent) => { + const audioDir = path.join(context.tempDir, THREAD_DIRS.AUDIO) + if (!fs.existsSync(audioDir)) fs.mkdirSync(audioDir, { recursive: true }) + + const audioPath = await ffmpegAdapter.toAudio(videoPath, audioDir, (percent) => { context.updateStatus(`Converting to audio... ${percent}%`) - }) + }, context.signal) context.savePreprocessing({ audioPath }) context.updateStatus('Audio extracted successfully.') @@ -56,11 +61,20 @@ export const extractRawTranscript: PipelineFunction = async (data, context) => { context.updateStatus('Extracting raw transcript...') const duration = await ffmpegAdapter.getVideoDuration(audioPath) - const { items: transcript, record } = await extractTranscript(audioPath, duration) - context.recordUsage(record) + const { items: transcript, rawResponseText, record } = await extractTranscript(audioPath, duration, undefined, context.signal) - const tempDir = context.tempDir - const rawTranscriptPath = path.join(tempDir, `raw_transcript.json`) + // Record usage immediately + await context.recordUsage(record) + + if (context.signal.aborted) return; + + const transcriptsDir = path.join(context.tempDir, THREAD_DIRS.TRANSCRIPTS) + if (!fs.existsSync(transcriptsDir)) fs.mkdirSync(transcriptsDir, { recursive: true }) + + const rawResponsePath = path.join(transcriptsDir, `raw_transcript_response.txt`) + fs.writeFileSync(rawResponsePath, rawResponseText) + + const rawTranscriptPath = path.join(transcriptsDir, `raw_transcript.json`) fs.writeFileSync(rawTranscriptPath, JSON.stringify(transcript, null, 2)) context.savePreprocessing({ rawTranscriptPath, transcriptPath: rawTranscriptPath }) @@ -82,13 +96,22 @@ export const extractCorrectedTranscript: PipelineFunction = async (data, context const transcriptJson = fs.readFileSync(rawTranscriptPath, 'utf-8') const rawTranscript = JSON.parse(transcriptJson) - const rawSrt = generateSRT(rawTranscript) + const rawTranscriptText = formatTranscript(rawTranscript) - const { items: transcript, record } = await extractTranscript(audioPath, duration, rawSrt) - context.recordUsage(record) + const { items: transcript, rawResponseText, record } = await extractTranscript(audioPath, duration, rawTranscriptText, context.signal) - const tempDir = context.tempDir - const correctedTranscriptPath = path.join(tempDir, `corrected_transcript.json`) + // Record usage immediately + await context.recordUsage(record) + + if (context.signal.aborted) return; + + const transcriptsDir = path.join(context.tempDir, THREAD_DIRS.TRANSCRIPTS) + if (!fs.existsSync(transcriptsDir)) fs.mkdirSync(transcriptsDir, { recursive: true }) + + const rawResponsePath = path.join(transcriptsDir, `corrected_transcript_response.txt`) + fs.writeFileSync(rawResponsePath, rawResponseText) + + const correctedTranscriptPath = path.join(transcriptsDir, `corrected_transcript.json`) fs.writeFileSync(correctedTranscriptPath, JSON.stringify(transcript, null, 2)) context.savePreprocessing({ correctedTranscriptPath, transcriptPath: correctedTranscriptPath }) @@ -112,16 +135,20 @@ export const extractSceneTiming: PipelineFunction = async (data, context) => { const detector = new SceneDetector() let scenes: Scene[] = [] try { - scenes = await detector.detectScenes(videoPath) + scenes = await detector.detectScenes(videoPath, context.signal) } catch (error) { - console.error('Scene detection failed:', error) + if (!context.signal.aborted) { + console.error('Scene detection failed:', error) + } context.updateStatus('Scene detection failed, proceeding without scenes.') context.next(data) return } - const tempDir = context.tempDir - const sceneTimesPath = path.join(tempDir, `scenes.json`) + const analysisDir = path.join(context.tempDir, THREAD_DIRS.ANALYSIS) + if (!fs.existsSync(analysisDir)) fs.mkdirSync(analysisDir, { recursive: true }) + + const sceneTimesPath = path.join(analysisDir, `scenes.json`) fs.writeFileSync(sceneTimesPath, JSON.stringify(scenes, null, 2)) context.savePreprocessing({ sceneTimesPath }) @@ -129,6 +156,8 @@ export const extractSceneTiming: PipelineFunction = async (data, context) => { context.next({ ...data, scenes }) } +const BATCH_SIZE = 50 + export const generateSceneDescription: PipelineFunction = async (data, context) => { const sceneTimesPath = context.preprocessing.sceneTimesPath if (!sceneTimesPath) { @@ -143,65 +172,192 @@ export const generateSceneDescription: PipelineFunction = async (data, context) context.updateStatus(`Generating descriptions for ${scenes.length} scenes...`) const gemini = GeminiAdapter.create() - // Use a cheap model for description - const modelName = GEMINI_MODEL_2_5_FLASH_LITE + // Read model from settings + const modelSettings = settingsManager.getModelSettings() + const modelName = modelSettings.selection['scene-description'] || GEMINI_MODEL_2_5_FLASH_LITE - const descriptions: { index: number, startTime: number, description: string }[] = [] - const framesDir = path.join(tempDir, 'frames') + const descriptions: { index: number, startTime: number, description: string, framePath: string }[] = [] + let framesDir = path.join(tempDir, THREAD_DIRS.FRAMES) if (!fs.existsSync(framesDir)) { fs.mkdirSync(framesDir) } - // Process in batches to avoid overwhelming (though sequential is safer for FFmpeg) - for (let i = 0; i < scenes.length; i++) { - const scene = scenes[i] - const midpoint = scene.startTime + (scene.duration / 2) + // Process in batches to reduce API overhead and improve speed + for (let i = 0; i < scenes.length; i += BATCH_SIZE) { + const batchScenes = scenes.slice(i, i + BATCH_SIZE) + const batchFramePaths: string[] = [] + const validBatchInfo: { scene: Scene, index: number, framePath: string }[] = [] + + context.updateStatus(`Analyzing scenes ${i + 1} to ${Math.min(i + BATCH_SIZE, scenes.length)} / ${scenes.length}...`) + + // 1. Extract Frames for the batch (sequential extraction is safer for FFmpeg resources) + for (let j = 0; j < batchScenes.length; j++) { + const scene = batchScenes[j] + const originalIndex = i + j + + // Skip very short scenes? + if (scene.duration < 1.0) continue; + + try { + const midpoint = scene.startTime + (scene.duration / 2) + const framePath = await ffmpegAdapter.extractFrame(videoPath, midpoint, framesDir, context.signal) + batchFramePaths.push(framePath) + validBatchInfo.push({ scene, index: originalIndex, framePath }) + } catch (error) { + if (!context.signal.aborted) { + console.error(`Failed to extract frame for scene ${originalIndex}:`, error) + } + } + } - // Skip very short scenes? - if (scene.duration < 1.0) continue; + if (validBatchInfo.length === 0) continue; try { - context.updateStatus(`analyzing scene ${i + 1}/${scenes.length}...`) + // 2. Upload Frames in parallel + const uploadPromises = batchFramePaths.map(fpath => gemini.uploadFile(fpath, 'image/jpeg')) + const frameUris = await Promise.all(uploadPromises) - // 1. Extract Frame - const framePath = await ffmpegAdapter.extractFrame(videoPath, midpoint, framesDir) + // 3. Describe Frames in batch + // Add context from previous batches to maintain continuity + let prompt = `I am providing you with ${validBatchInfo.length} chronological frames from a video. +For EACH frame, provide a concise one-sentence description focusing on visual action, setting, and atmosphere. +Maintain consistency in identifying people, objects, and settings across the frames. - // 2. Upload Frame - const frameUri = await gemini.uploadFile(framePath, 'image/jpeg') +Return the descriptions as an array of strings in the exact same order as the images provided.` - // 3. Describe Frame - // Add context from previous scenes to maintain continuity - let prompt = BASE_SCENE_PROMPT if (descriptions.length > 0) { const recentContext = descriptions.slice(-3).map(d => d.description).join('\n- ') prompt += `\n\nContext from previous scenes (use this to identify recurring people/settings if applicable):\n- ${recentContext}` } - const { text, record } = await gemini.generateDescriptionFromImage( + const schema = { + type: 'object', + properties: { + descriptions: { + type: 'array', + items: { type: 'string' }, + description: 'One sentence description for each frame, in order.' + } + }, + required: ['descriptions'] + } + + const { data: result, record } = await gemini.generateStructuredFromImages<{ descriptions: string[] }>( modelName, prompt, - frameUri + frameUris, + schema, + context.signal ) - context.recordUsage(record) - descriptions.push({ - index: i, - startTime: scene.startTime, - description: text.trim() + // Record usage immediately + await context.recordUsage(record) + + if (context.signal.aborted) return; + + // 4. Map results back to scenes + result.descriptions.forEach((text, index) => { + const info = validBatchInfo[index] + if (info) { + descriptions.push({ + index: info.index, + startTime: info.scene.startTime, + description: text.trim(), + framePath: info.framePath + }) + } + }) + + // 5. Save frames discovered so far to the thread metadata so the UI updates in real-time + const currentFrames = fs.readdirSync(framesDir) + .filter(f => f.endsWith('.jpg') || f.endsWith('.jpeg')) + .map(f => path.join(framesDir, f)) + + await context.savePreprocessing({ + 'reference-frames': currentFrames }) - // Cleanup frame to save space - fs.unlinkSync(framePath) + // 6. DO NOT cleanup frames - we want to keep them as reference-frames + // batchFramePaths.forEach(fpath => { + // try { if (fs.existsSync(fpath)) fs.unlinkSync(fpath) } catch (e) { } + // }) } catch (error) { - console.error(`Failed to describe scene ${i}:`, error) + if (!context.signal.aborted) { + console.error(`Failed to describe batch starting at index ${i}:`, error) + } } } - const sceneDescriptionsPath = path.join(tempDir, `scene_descriptions.json`) + const analysisDir = path.join(tempDir, THREAD_DIRS.ANALYSIS) + if (!fs.existsSync(analysisDir)) fs.mkdirSync(analysisDir, { recursive: true }) + + const sceneDescriptionsPath = path.join(analysisDir, `scene_descriptions.json`) fs.writeFileSync(sceneDescriptionsPath, JSON.stringify(descriptions, null, 2)) - context.savePreprocessing({ sceneDescriptionsPath }) + // Collect all retained frames from the frames directory + framesDir = path.join(tempDir, THREAD_DIRS.FRAMES) + let allFrames: string[] = [] + if (fs.existsSync(framesDir)) { + allFrames = fs.readdirSync(framesDir) + .filter(f => f.endsWith('.jpg') || f.endsWith('.jpeg')) + .map(f => path.join(framesDir, f)) + } + + context.savePreprocessing({ + sceneDescriptionsPath, + 'reference-frames': allFrames + }) context.updateStatus('Scene descriptions generated.') context.next({ ...data, sceneDescriptions: descriptions }) } + + +// Wait functions for pipeline to use +export const waitForEnsureLowResolution: PipelineFunction = async (data, context) => { + console.log(`[EXTRACTION PHASE] Entering waitForEnsureLowResolution`) + await context.updateStatus('Ensuring optimal video resolution...') + await context.waitForTask('downscale') + console.log(`[EXTRACTION PHASE] Leaving waitForEnsureLowResolution`) + context.next(data) +} + +export const waitForConvertToAudio: PipelineFunction = async (data, context) => { + console.log(`[EXTRACTION PHASE] Entering waitForConvertToAudio`) + await context.updateStatus('Waiting for audio extraction...') + await context.waitForTask('audio') + console.log(`[EXTRACTION PHASE] Leaving waitForConvertToAudio`) + context.next(data) +} + +export const waitForExtractRawTranscript: PipelineFunction = async (data, context) => { + console.log(`[EXTRACTION PHASE] Entering waitForExtractRawTranscript`) + await context.updateStatus('Waiting for raw transcript...') + await context.waitForTask('rawTranscript') + console.log(`[EXTRACTION PHASE] Leaving waitForExtractRawTranscript`) + context.next(data) +} + +export const waitForExtractCorrectedTranscript: PipelineFunction = async (data, context) => { + console.log(`[EXTRACTION PHASE] Entering waitForExtractCorrectedTranscript`) + await context.updateStatus('Waiting for transcript refinement...') + await context.waitForTask('correctedTranscript') + console.log(`[EXTRACTION PHASE] Leaving waitForExtractCorrectedTranscript`) + context.next(data) +} + +export const waitForExtractSceneTiming: PipelineFunction = async (data, context) => { + console.log(`[EXTRACTION PHASE] Entering waitForExtractSceneTiming`) + await context.updateStatus('Waiting for scene timing detection...') + await context.waitForTask('sceneTiming') + console.log(`[EXTRACTION PHASE] Leaving waitForExtractSceneTiming`) + context.next(data) +} + +export const waitForGenerateSceneDescription: PipelineFunction = async (data, context) => { + console.log(`[EXTRACTION PHASE] Entering waitForGenerateSceneDescription`) + await context.updateStatus('Waiting for scene descriptions...') + await context.waitForTask('sceneDescriptions') + console.log(`[EXTRACTION PHASE] Leaving waitForGenerateSceneDescription`) + context.next(data) +} diff --git a/src/main/pipeline/phases/generation.ts b/src/main/pipeline/phases/generation.ts index eb3e7a7..f68774e 100644 --- a/src/main/pipeline/phases/generation.ts +++ b/src/main/pipeline/phases/generation.ts @@ -1,39 +1,29 @@ import { PipelineFunction } from '../index' import { generateTimeline } from '../../timeline' -import { enrichTranscriptWithScenes, SceneDescription } from '../../timeline/enrichment' -import * as ffmpegAdapter from '../../ffmpeg' import fs from 'fs' -import { TranscriptItem } from 'src/main/gemini/utils' +import { EnrichedTimelineSegment } from '../../../shared/types' + + +export const waitForEnrichTranscript: PipelineFunction = async (data, context) => { + console.log(`[GENERATION PHASE] Entering waitForEnrichTranscript`) + await context.updateStatus('Waiting for visual and text unification...') + await context.waitForTask('enrichment') + console.log(`[GENERATION PHASE] Leaving waitForEnrichTranscript`) + context.next(data) +} export const buildShorterTimeline: PipelineFunction = async (data, context) => { context.updateStatus('Preparing for timeline generation...') - const transcriptPath = context.preprocessing?.correctedTranscriptPath || context.preprocessing?.transcriptPath || context.preprocessing?.rawTranscriptPath + const transcriptPath = context.preprocessing?.enrichedTranscriptPath || context.preprocessing?.correctedTranscriptPath || context.preprocessing?.transcriptPath || context.preprocessing?.rawTranscriptPath + if (!transcriptPath || !fs.existsSync(transcriptPath)) { throw new Error('Transcript file not found. Cannot generate timeline.') } const transcriptJson = fs.readFileSync(transcriptPath, 'utf-8') - let transcript = JSON.parse(transcriptJson) as TranscriptItem[] + const transcript = JSON.parse(transcriptJson) as EnrichedTimelineSegment[] - // Enrich with scenes if available - if (context.preprocessing.sceneDescriptionsPath && fs.existsSync(context.preprocessing.sceneDescriptionsPath)) { - try { - const scenesJson = fs.readFileSync(context.preprocessing.sceneDescriptionsPath, 'utf-8') - const sceneDescriptions: SceneDescription[] = JSON.parse(scenesJson) - - // We need total duration to close the last gap - // Ideally we get it from context or ffmpeg, but for now we can infer or use a large number - // Actually we can get it from ffmpegAdapter if we want, or just rely on the last timestamp of transcript - // Let's try to get it from metadata if possible, or just use the last transcript end time + gap - const videoDuration = await ffmpegAdapter.getVideoDuration(context.preprocessing.audioPath!) - - transcript = enrichTranscriptWithScenes(transcript, sceneDescriptions, videoDuration) - context.updateStatus(`Enriched transcript with visual scene data.`) - } catch (e) { - console.warn("Failed to enrich transcript:", e) - } - } const userExpectation = context.intentResult?.content || context.context || "Create a highlight reel." const targetDuration = context.intentResult?.duration || 30 // Default 30 seconds @@ -53,7 +43,8 @@ export const buildShorterTimeline: PipelineFunction = async (data, context) => { modelName, mode, onUpdateStatus: (status) => context.updateStatus(status), - onRecordUsage: (record) => context.recordUsage(record) + onRecordUsage: (record) => context.recordUsage(record), + signal: context.signal }) if (shorterTimeline.length === 0) { @@ -64,11 +55,18 @@ export const buildShorterTimeline: PipelineFunction = async (data, context) => { // Finish Pipeline with the generated timeline context.updateStatus('Processing complete. Short timeline generated.') + if (context.signal.aborted) { + console.log(`[GENERATION PHASE] Generation aborted before next step.`) + return; + } + // Ready for the assembly phase (pass timeline in data) context.next({ ...data, timeline: shorterTimeline }) } catch (error) { - console.error('Error in buildShorterTimeline:', error) + if (!context.signal.aborted) { + console.error('Error in buildShorterTimeline:', error) + } // We should probably rely on pipeline error handling, but here we can add context throw error } diff --git a/src/main/pipeline/phases/image-extraction.ts b/src/main/pipeline/phases/image-extraction.ts new file mode 100644 index 0000000..786c053 --- /dev/null +++ b/src/main/pipeline/phases/image-extraction.ts @@ -0,0 +1,93 @@ +import { PipelineFunction } from '../index' +import { GeminiAdapter } from '../../gemini/adapter' +import fs from 'fs' +import path from 'path' +import { settingsManager } from '../../settings' +import { THREAD_DIRS } from '../../constants/paths' + +export const extractImageData: PipelineFunction = async (_data, context) => { + const sourceImages = context.preprocessing.sourceImages || [] + const referenceFrames = context.preprocessing['reference-frames'] || [] + const allImages = [...sourceImages, ...referenceFrames] + + if (allImages.length === 0) { + context.next(_data) + return + } + + context.updateStatus(`Extracting data from ${allImages.length} images...`) + + const gemini = GeminiAdapter.create() + const modelSettings = settingsManager.getModelSettings() + const modelName = modelSettings.selection['image-extraction'] + + const BATCH_SIZE = 15 // Smaller batch for detailed extraction + const imageTexts: Record = {} + + for (let i = 0; i < allImages.length; i += BATCH_SIZE) { + const batch = allImages.slice(i, i + BATCH_SIZE) + context.updateStatus(`Analyzing images ${i + 1} to ${Math.min(i + BATCH_SIZE, allImages.length)} / ${allImages.length}...`) + + try { + const uploadPromises = batch.map(fpath => gemini.uploadFile(fpath, 'image/jpeg')) + const uris = await Promise.all(uploadPromises) + + const prompt = `For EACH of these ${batch.length} images (referenced by their order 0 to ${batch.length - 1}), provide a detailed textual description. +Include any visible text (OCR), key objects, people, setting, and atmosphere. +Be precise as this text will be used by an AI to decide which images to use for a creative task. +Return the results as a JSON object with a 'data' array of objects, each containing 'index' (number) and 'description' (string).` + + const schema = { + type: 'object', + properties: { + data: { + type: 'array', + items: { + type: 'object', + properties: { + index: { type: 'number' }, + description: { type: 'string' } + }, + required: ['index', 'description'] + } + } + }, + required: ['data'] + } + + const { data: result, record } = await gemini.generateStructuredFromImages<{ data: Array<{ index: number, description: string }> }>( + modelName, + prompt, + uris, + schema, + context.signal + ) + + await context.recordUsage(record) + + if (context.signal.aborted) return + + result.data.forEach((item) => { + const realIndex = i + item.index + if (allImages[realIndex]) { + imageTexts[allImages[realIndex]] = item.description + } + }) + + } catch (error) { + if (!context.signal.aborted) { + console.error(`Failed to extract data for batch ${i}:`, error) + } + } + } + + const analysisDir = path.join(context.tempDir, THREAD_DIRS.ANALYSIS) + if (!fs.existsSync(analysisDir)) fs.mkdirSync(analysisDir, { recursive: true }) + + const imageTextPath = path.join(analysisDir, 'image_data.json') + fs.writeFileSync(imageTextPath, JSON.stringify(imageTexts, null, 2)) + + await context.savePreprocessing({ imageTextPath }) + context.updateStatus('Image data extraction complete.') + context.next({ ..._data, imageTexts }) +} diff --git a/src/main/pipeline/phases/image-generation.ts b/src/main/pipeline/phases/image-generation.ts new file mode 100644 index 0000000..dfe2de2 --- /dev/null +++ b/src/main/pipeline/phases/image-generation.ts @@ -0,0 +1,109 @@ +import { PipelineFunction } from '../index' +import { GeminiAdapter } from '../../gemini/adapter' +import { settingsManager } from '../../settings' +import { threadManager } from '../../threads' +import path from 'path' +import fs from 'fs' +import { FileType, MessageRole } from '../../../shared/types' +import { THREAD_DIRS } from '../../constants/paths' + +export const generateOutputImage: PipelineFunction = async (data, context) => { + context.updateStatus('Generating final image...') + + const gemini = GeminiAdapter.create() + const modelSettings = settingsManager.getModelSettings() + const modelName = modelSettings.selection['image-generation'] + + // 1. Detect iteration/refinement + let previousFiles: string[] = [] + let isIteration = false + if (context.editRefId) { + const thread = threadManager.getThread(context.threadId) + let refMsg = thread?.messages.find(m => m.id === context.editRefId) + + // If the immediate parent is a User message, the files are in its parent (the previous AI result) + if (refMsg && refMsg.role === MessageRole.User && refMsg.editRefId) { + const grandParentId = refMsg.editRefId + refMsg = thread?.messages.find(m => m.id === grandParentId) + } + + if (refMsg && refMsg.files && refMsg.files.length > 0) { + isIteration = true + previousFiles = refMsg.files + .filter(f => f.type === FileType.Actual || f.type === FileType.Preview) + .map(f => { + let raw = f.url.replace('media://', '') + return path.normalize(raw) + }) + console.log(`[IMAGE-GEN] Iteration detected. Found ${previousFiles.length} files from message ${refMsg.id}`) + } + } + + const intentPrompt = context.intentResult?.content || 'Generate a creative image based on the provided ones.' + const selectedReferenceImages = data.selectedReferenceImages || data.selectedImagePaths || [] + + if (selectedReferenceImages.length === 0 && previousFiles.length === 0) { + throw new Error('No images selected for generation. Please check the intent analysis.') + } + + // 2. Combine reference images, prioritizing previous results if iterating + // Use a max of 5 images to keep prompt complexity manageable + const allReferenceImages = Array.from(new Set([...previousFiles, ...selectedReferenceImages])).slice(0, 5) + + const generatorPrompt = isIteration + ? `Refinement Request: "${intentPrompt}"\n\nPlease update the previous result based on this request while maintaining visual consistency with the original source images.` + : intentPrompt + + const systemInstruction = `You are an expert AI image generator and editor. +Your ONLY goal is to output a single image that fulfills the user prompt based on the visual context of the provided images. + +CRITICAL RULES: +1. VISUAL CONSISTENCY: Maintain consistent appearance for subjects and styles seen in the reference images. +2. REFINEMENT: If a previous result is provided (usually the first image), treat this as an "edit" or "refinement" task. Focus on applying the requested changes while keeping the overall composition and subjects consistent with the previous image. +3. OUTPUT: DO NOT output ANY text or explanation—ONLY raw image data.` + + try { + const resultsDir = path.join(context.tempDir, THREAD_DIRS.GENERATED_IMAGES) + if (!fs.existsSync(resultsDir)) fs.mkdirSync(resultsDir, { recursive: true }) + + const fileName = `generated_image_${Date.now()}.png` + const destPath = path.join(resultsDir, fileName) + + const { path: savedPath, record } = await gemini.generateImage( + modelName, + generatorPrompt, + destPath, + allReferenceImages, + systemInstruction, + context.signal, + { includeThinking: true } + ) + + await context.recordUsage(record) + + if (context.signal.aborted) return + + context.updateStatus('Generation complete.') + context.finish( + `I have generated a new image based on your request: ${generatorPrompt.substring(0, 50)}...`, + undefined, + undefined, + { + resultType: 'image', + files: [ + { url: savedPath, type: FileType.Actual }, + ...allReferenceImages.filter(p => p !== savedPath).map((path: string) => ({ + url: path.startsWith('media://') ? path : `media://${path}`, + type: FileType.Preview + })) + ] + } + ) + + } catch (error) { + if (!context.signal.aborted) { + console.error(`Failed to generate output image:`, error) + } + throw error + } +} diff --git a/src/main/pipeline/phases/image-intent.ts b/src/main/pipeline/phases/image-intent.ts new file mode 100644 index 0000000..95be278 --- /dev/null +++ b/src/main/pipeline/phases/image-intent.ts @@ -0,0 +1,120 @@ +import { PipelineFunction } from '../index' +import { GeminiAdapter } from '../../gemini/adapter' +import fs from 'fs' +import { IntentResult } from '../../../shared/types' +import { settingsManager } from '../../settings' + +const IMAGE_INTENT_SYSTEM_INSTRUCTION = ` +Model Role: +You are an AI assistant for a creative image editor. Your goal is to understand the user's intent based on their latest message, the conversation history, the detailed textual descriptions of all images provided in the collection, and optionally, any attached images provided by the user. + +Task: +You must decide between two types of actions: +1. "text": Conversational response. Use this for general questions, proposing a creative idea, or asking for final confirmation. +2. "generate-image": Signal to actually generate a new image based on the collection. + +Data Provided: +- COLLECTION: A JSON object mapping file paths to detailed descriptions of those images. +- PROMPT: The user's request. +- ATTACHED IMAGES: (Optional) Actual images provided by the user in the current message or history. + +Attached Images: +- If the user provides images, they are likely intended as reference material for the generation (e.g., "make something in this style", "mix this image with my collection"). +- Use these images to better understand what the user is referring to. + +Rules (STRICT ENFORCEMENT): +- NEVER trigger "generate-image" if the request is ambiguous. +- ONLY trigger "generate-image" if: + a) The user gives a direct, unambiguous COMMAND. + b) The user explicitly confirms a previously proposed creative idea. +- If "generate-image" is triggered, you must: + a) Select the SPECIFIC images from the COLLECTION that are relevant to the request. + b) Create a DETAILED technical prompt for a creative image generator (like Gemini Image 3). + c) This prompt should specify style, composition, lighting, and how to merge the elements from the selected images AND any provided ATTACHED IMAGES. + d) PERSON NAMES: DO NOT mention specific real-world names (e.g., 'Olga Loiek') in the 'content' field. Instead, refer to them using generic descriptors based on the images, such as 'the speaker', 'the subject', 'the person in the video', or 'the main figure'. This is to avoid triggering safety/privacy filters. You can refer to 'Image X' or 'Image index Y' to point to specific people. + +Respond ONLY with a JSON object following this schema: +{ + "type": "text" | "generate-image", + "content": "A detailed idea/prompt for the image generator (if generate-image) OR the final text answer (if text)", + "selectedIndices": number[] (indices into the provided collection array for images to be used - 0-indexed) +} +` + +const IMAGE_INTENT_SCHEMA = { + type: 'object', + properties: { + type: { type: 'string', enum: ['text', 'generate-image'] }, + content: { type: 'string' }, + selectedIndices: { + type: 'array', + items: { type: 'number' } + } + }, + required: ['type', 'content', 'selectedIndices'] +} + +export const determineImageIntent: PipelineFunction = async (_data, context) => { + context.updateStatus('Analyzing image intent...') + + const imageDataPath = context.preprocessing.imageTextPath + if (!imageDataPath || !fs.existsSync(imageDataPath)) { + throw new Error('Image data not found. Please wait for extraction to complete.') + } + + const imageTexts = JSON.parse(fs.readFileSync(imageDataPath, 'utf-8')) + const imagePaths = Object.keys(imageTexts) + + const collectionText = Object.entries(imageTexts) + .map(([path, text], index) => `Image ${index}: ${text}`) + .join('\n\n') + + const contextLines = context.context.trim().split('\n') + const lastUserPrompt = contextLines.pop() || '' + + const userPrompt = ` +COLLECTION of Images: +${collectionText} + +Conversation History: +${context.context} + +User Prompt: +${lastUserPrompt} + +(Note: If the user provided any attached images, they are passed as visual context to you.) +` + console.log(`[IMAGE-INTENT] Sending prompt to AI. User prompt detected as: "${lastUserPrompt}"`) + + const uniqueImages = Array.from(new Set(context.attachedImages || [])) + const limitedImages = uniqueImages.slice(-8) + + const adapter = GeminiAdapter.create() + const modelSettings = settingsManager.getModelSettings() + const modelName = modelSettings.selection['image-intent'] + + const { data: result, record } = await adapter.generateStructuredText( + modelName, + userPrompt, + IMAGE_INTENT_SCHEMA, + IMAGE_INTENT_SYSTEM_INSTRUCTION, + context.signal, + limitedImages, + { includeThinking: true } + ) + + console.log(`[IMAGE-INTENT] AI Result: type=${result.type}, content length=${result.content.length}, selectedIndices=${JSON.stringify(result.selectedIndices)}`) + + await context.recordUsage(record) + + if (context.signal.aborted) return + + context.intentResult = result + + if (result.type === 'text') { + context.finish(result.content) + } else { + context.updateStatus(`Intent recognized: Selecting ${result.selectedIndices?.length || 0} images...`) + context.next({ ..._data }) + } +} diff --git a/src/main/pipeline/phases/intent.ts b/src/main/pipeline/phases/intent.ts index 859ec1d..28b52ce 100644 --- a/src/main/pipeline/phases/intent.ts +++ b/src/main/pipeline/phases/intent.ts @@ -1,31 +1,44 @@ import { PipelineFunction } from '../index' import { GeminiAdapter } from '../../gemini/adapter' import { IntentResult } from '../../../shared/types' -import { TranscriptItem, generateSRT } from '../../gemini/utils' +import { TranscriptItem, formatTranscript } from '../../gemini/utils' import * as ffmpegAdapter from '../../ffmpeg' import fs from 'fs' const INTENT_SYSTEM_INSTRUCTION = ` Model Role: -You are an AI assistant for a video editing tool. Your goal is to understand the user's intent based on their latest message, the conversation history, the video transcript, and optionally, a reference timeline (a list of scenes to be extracted from the original video). +You are an AI assistant for a video editing tool. Your goal is to understand the user's intent based on their latest message, the conversation history, the video transcript, and optionally, a reference timeline and any attached images provided by the user. Task: You must decide between two types of actions: -1. "text": Conversational response. Use this for general questions, proposing a summary plan, or asking for final confirmation. This is the DEFAULT and preferred action. +1. "text": Conversational response. Use this for general questions, proposing a summary plan, proposing a thumbnail idea, or asking for final confirmation. This is the DEFAULT and preferred action. 2. "generate-timeline": Signal to actually build the video. +3. "generate-thumbnail": Signal to generate a thumbnail. Reference Timeline (Edit Mode): - If a "REFERENCE TIMELINE" is provided below, it means the user is currently editing an existing summary. - Your goal is to decide whether to update/modify this timeline or just answer the user's question about it. - If the user asks to "change", "add", "remove", "extend", or "refine" parts of it, trigger "generate-timeline". +Attached Images: +- If the user provides images (frames from the video or uploaded assets), they are likely intended as reference material for a thumbnail, cover, or as a specific moment they want included in the video summary. +- Use these images to better understand what the user is referring to (e.g., "make a cover like this"). + Confirmation Rules (STRICT ENFORCEMENT): - NEVER trigger "generate-timeline" for suggestive or planning phrases like "let's make a summary", "can you create a highlights clip", "how about a summary", or "I want to see the key moments", etc. - For any of the above, use "text" to describe what you will include in the summary (e.g. "I will create a 30s summary focusing on [X].") and ask: "Shall I proceed with generating this video?". - ONLY trigger "generate-timeline" if: a) The user gives a direct, unambiguous COMMAND including a duration (e.g., "Generate/Create a 30s video now"). - b) The user explicitly confirms a proposal you just made (e.g., "Yes", "Go ahead", "Do it", "Proceed"). + b) The user explicitly confirms a previously proposed VIDEO/SUMMARY idea (e.g., "Yes", "Go ahead", "Do it"). If the conversation history shows your last response was a summary proposal, interpret "Yes/Go ahead" as "generate-timeline". c) The user explicitly asks to MODIFY the existing REFERENCE TIMELINE (e.g., "Change the middle part to show X", "Make it longer"). + +Thumbnail Rules: +- If the user asks for a thumbnail (e.g., "Make a thumbnail", "Suggest a thumbnail", "How about a thumbnail for this video?"), your FIRST step must be to propose an idea using "type": "text". +- Propose a specific idea based on the video content (e.g., "I suggest a thumbnail showing the logo reveal at 00:45 with a 'New Product' text. Shall I generate it?"). +- ONLY trigger "generate-thumbnail" if: + a) The user explicitly confirms a previously proposed THUMBNAIL idea (e.g., "Yes", "Go ahead", "Do it", "Looks good", "cool", "coll"). If the conversation history shows your last response was a thumbnail proposal, interpret "Yes/Go ahead" as "generate-thumbnail". + b) The user gives a direct, unambiguous COMMAND for a specific thumbnail (e.g., "Generate a thumbnail with a blue background and text 'Hello'"). + c) The user provides specific images and asks to "make a cover/thumbnail with these". In this case, you can trigger "generate-thumbnail" immediately as the intent is clear and visual reference is provided. - If the user asks "Tell me about the video", provide a detailed text description in the chat and do NOT trigger generation. Behavioral Guidelines: @@ -35,22 +48,30 @@ Behavioral Guidelines: Respond ONLY with a JSON object following this schema: { - "type": "text" | "generate-timeline", - "content": "A detailed description of what to generate (if generate-timeline) OR the final text answer (if text)", - "duration": number (only if type is "generate-timeline") + "type": "text" | "generate-timeline" | "generate-thumbnail", + "content": "A detailed idea/prompt for the thumbnail (if generate-thumbnail) OR a description for timeline builder (if generate-timeline) OR the final text answer (if text)", + "duration": number (only if type is "generate-timeline"), + "selectedIndices": number[] (ONLY if type is "generate-thumbnail" and you want to use specific scenes from the video as reference. Indices are 0-based based on the ENRICHED TIMELINE SEGMENTS provided) } Specific rules for 'content' field: - If type is 'text': This is the message shown directly to the user. -- If type is 'generate-timeline': This is a COMPREHENSIVE and DETAILED technical description for the timeline builder agent. It should include all user preferences, specific moments mentioned, style constraints, and context from previous iterations. -- CRITICAL: When editing an existing timeline, specify EXACTLY which parts to keep, remove, or replace. The goal is maximum consistency with the REFERENCE TIMELINE except for the requested changes. It will NOT be shown to the user.` +- If type is 'generate-timeline': This is a COMPREHENSIVE and DETAILED technical description for the timeline builder agent. +- If type is 'generate-thumbnail': This is a COMPREHENSIVE and DETAILED technical description for the thumbnail generator. Include visual elements, frames to extract (YOU MUST list specific timestamps in [HH:MM:SS] format for at least 2-3 relevant scenes), and any overlay text. +- CRITICAL: When editing an existing timeline or thumbnail, specify EXACTLY which parts to keep, remove, or replace. The goal is maximum consistency with the REFERENCE TIMELINE except for the requested changes. It will NOT be shown to the user. 'content' for 'generate-thumbnail' MUST be technical and precise. +- PERSON NAMES: DO NOT mention specific real-world names (e.g., 'Olga Loiek') in the 'content' field for 'generate-thumbnail'. Instead, refer to them using generic descriptors based on the reference frames, such as 'the speaker', 'the subject', 'the person in the video', or 'the main figure'. This is to avoid triggering safety/privacy filters. You can refer to 'Scene X' or 'Image Y' to point to specific people.` const INTENT_SCHEMA = { type: 'object', properties: { - type: { type: 'string', enum: ['text', 'generate-timeline'] }, + type: { type: 'string', enum: ['text', 'generate-timeline', 'generate-thumbnail'] }, content: { type: 'string' }, - duration: { type: 'number' } + duration: { type: 'number' }, + selectedIndices: { + type: 'array', + items: { type: 'number' }, + description: 'Indices of scenes from the enriched timeline to be used as visual references for the thumbnail/cover.' + } }, required: ['type', 'content'] } @@ -58,21 +79,26 @@ const INTENT_SCHEMA = { export const determineIntent: PipelineFunction = async (data, context) => { context.updateStatus('Analyzing your request...') + // 1. Gather Video Context (Timeline Segments OR Transcript) + let videoContextText = '' + const sceneDescriptionsPath = context.preprocessing.sceneDescriptionsPath const transcriptPath = context.preprocessing.rawTranscriptPath - let transcript: TranscriptItem[] = [] - if (transcriptPath && fs.existsSync(transcriptPath)) { - const content = fs.readFileSync(transcriptPath, 'utf-8') - transcript = JSON.parse(content) + if (sceneDescriptionsPath && fs.existsSync(sceneDescriptionsPath)) { + // Use Enriched Timeline Segments as primary visual-first context + const scenes = JSON.parse(fs.readFileSync(sceneDescriptionsPath, 'utf-8')); + videoContextText = `ENRICHED TIMELINE SEGMENTS (Scene Descriptions):\n` + + scenes.map((s: any, idx: number) => `Scene ${idx+1} [${s.start} - ${s.end}]: ${s.description}`).join('\n'); + } else if (transcriptPath && fs.existsSync(transcriptPath)) { + // Fallback to audio transcript if scenes are missing + const transcript: TranscriptItem[] = JSON.parse(fs.readFileSync(transcriptPath, 'utf-8')); + videoContextText = `AUDIO TRANSCRIPT:\n` + formatTranscript(transcript); } - // Include timestamps for better context in intent analysis - const rawSrt = generateSRT(transcript); - - // Get video duration from ffmpeg (source of truth) + // 2. Technical Metadata const videoDuration = await ffmpegAdapter.getVideoDuration(context.videoPath) - // Format base timeline if available + // 3. Format base timeline if available (edit mode) let baseTimelineContext = '' if (context.baseTimeline && Array.isArray(context.baseTimeline) && context.baseTimeline.length > 0) { baseTimelineContext = ` @@ -81,42 +107,56 @@ ${context.baseTimeline.map((s: any) => `• [${s.start} --> ${s.end}] (${s.durat ` } - const userPrompt = `The original video is approximately ${videoDuration} seconds long. + // 4. Build Final User Prompt + const userPrompt = `Video Duration: ${videoDuration}s -${baseTimelineContext} +${videoContextText} -START OF TRANSCRIPT (keep in mind as reference): -${rawSrt} -END OF TRANSCRIPT +${baseTimelineContext} Conversation History: ${context.context} -END OF CONVERSATION HISTORY ` + // 5. Limit and Deduplicate Images (Base64 is expensive, limit to last 8) + const uniqueImages = Array.from(new Set(context.attachedImages || [])) + const limitedImages = uniqueImages.slice(-8) + try { const adapter = GeminiAdapter.create() const modelSettings = (await import('../../settings')).settingsManager.getModelSettings() const modelName = modelSettings.selection['intent'] + const { data: result, record } = await adapter.generateStructuredText( modelName, userPrompt, INTENT_SCHEMA, - INTENT_SYSTEM_INSTRUCTION + INTENT_SYSTEM_INSTRUCTION, + context.signal, + limitedImages, + { includeThinking: true } ) - context.recordUsage(record) + // Record usage immediately + await context.recordUsage(record) + + if (context.signal.aborted) return; context.intentResult = result if (result.type === 'text') { context.finish(result.content) + } else if (result.type === 'generate-thumbnail') { + context.updateStatus(`Intent recognized: Preparing thumbnail: ${result.content.substring(0, 50)}...`) + context.next(data) } else { context.updateStatus(`Intent recognized: Generating a ${result.duration}s video...`) context.next(data) } } catch (error) { - console.error('Error in determineIntent:', error) + if (!context.signal.aborted) { + console.error('Error in determineIntent:', error) + } // Fallback to timeline if intent analysis fails context.intentResult = { type: 'generate-timeline', content: 'Create a video highlighting key moments.', duration: 30 } context.next(data) diff --git a/src/main/pipeline/phases/supply.ts b/src/main/pipeline/phases/supply.ts new file mode 100644 index 0000000..217ab76 --- /dev/null +++ b/src/main/pipeline/phases/supply.ts @@ -0,0 +1,77 @@ +import { PipelineFunction } from '../index' +import fs from 'fs' + +export const supplyController: PipelineFunction = async (data, context) => { + context.updateStatus('Managing reference images...') + + // Case 1: User provided attachments + // Rule: If user provide frames, then none for auto select. + if (context.attachedImages && context.attachedImages.length > 0) { + console.log(`[SUPPLY] User provided ${context.attachedImages.length} attachments. Skipping auto-selection from video.`) + context.next({ ...data, selectedReferenceImages: context.attachedImages }) + return + } + + // Case 2: No attachments, handle auto-selection from video frames or source images + const referenceFrames = context.preprocessing?.['reference-frames'] || [] + const sourceImages = context.preprocessing?.['sourceImages'] || [] + const isImageThread = context.preprocessing?.['imageTextPath'] !== undefined + + const intentResult = context.intentResult + + if (!referenceFrames.length && !sourceImages.length) { + console.log('[SUPPLY] No reference material available in project.') + context.next({ ...data, selectedReferenceImages: [] }) + return + } + + // Rule: if user wont provide frames, ai must select a few frames itself. + const selectedIndices = intentResult?.selectedIndices || [] + + if (selectedIndices.length > 0) { + console.log(`[SUPPLY] AI selected ${selectedIndices.length} items for reference.`) + + if (isImageThread) { + const allImagesPool = [...sourceImages, ...referenceFrames] + const selectedPaths = selectedIndices + .map(idx => allImagesPool[idx]) + .filter(p => p && fs.existsSync(p)) + console.log(`[SUPPLY] Resolved ${selectedPaths.length} images from pool of ${allImagesPool.length}.`) + context.next({ ...data, selectedReferenceImages: selectedPaths }) + return + } + + // Map indices to video frames + const sceneDescriptionPath = context.preprocessing.sceneDescriptionsPath + if (sceneDescriptionPath && fs.existsSync(sceneDescriptionPath)) { + const scenes: any[] = JSON.parse(fs.readFileSync(sceneDescriptionPath, 'utf-8')); + const selectedFramePaths: string[] = [] + + for (const idx of selectedIndices) { + const scene = scenes.find(s => s.index === idx) + const frame = scene?.framePath + if (frame && fs.existsSync(frame)) { + selectedFramePaths.push(frame) + } + } + + console.log(`[SUPPLY] Resolved ${selectedFramePaths.length} video frames from indices.`) + context.next({ ...data, selectedReferenceImages: selectedFramePaths }) + return + } + } + + // Fallback Case: No attachments and no indices (or missing metadata) + // Pick some representative frames/images + const pool = isImageThread ? [...sourceImages, ...referenceFrames] : referenceFrames + console.log(`[SUPPLY] No specific selection. Picking representative items from pool of ${pool.length}.`) + const fallbackIndices = pool.length <= 5 + ? pool.map((_, i) => i) + : [0, Math.floor(pool.length / 2), pool.length - 1] + + const fallbackPaths = fallbackIndices + .map(i => pool[i]) + .filter(p => p && fs.existsSync(p)) + + context.next({ ...data, selectedReferenceImages: fallbackPaths }) +} diff --git a/src/main/pipeline/phases/thumbnail.ts b/src/main/pipeline/phases/thumbnail.ts new file mode 100644 index 0000000..38dc9aa --- /dev/null +++ b/src/main/pipeline/phases/thumbnail.ts @@ -0,0 +1,141 @@ +import { PipelineFunction } from '../index' +import { GeminiAdapter } from '../../gemini/adapter' +import { threadManager } from '../../threads' +import * as ffmpegAdapter from '../../ffmpeg' +import fs from 'fs' +import path from 'path' +import { FileType, MessageRole, EnrichedTimelineSegment } from '../../../shared/types' +import { THREAD_DIRS } from '../../constants/paths' + + +export const generateThumbnail: PipelineFunction = async (data, context) => { + context.updateStatus('Preparing to generate thumbnail...') + + const intent = context.intentResult + if (!intent || intent.type !== 'generate-thumbnail') { + return context.next(data) + } + + // 1.5. Check for previous context if this is an edit/adjustment + let previousFiles: string[] = [] + let isIteration = false + if (context.editRefId) { + const thread = threadManager.getThread(context.threadId) + let refMsg = thread?.messages.find(m => m.id === context.editRefId) + + // If the immediate parent is a User message, the files are in its parent (the previous AI result) + if (refMsg && refMsg.role === MessageRole.User && refMsg.editRefId) { + const grandParentId = refMsg.editRefId + console.log(`[THUMBNAIL] Current ref node ${context.editRefId} is a User message. Jumping to grandparent ${grandParentId}`) + refMsg = thread?.messages.find(m => m.id === grandParentId) + } + + if (refMsg && refMsg.files && refMsg.files.length > 0) { + isIteration = true + previousFiles = refMsg.files + .filter(f => f.type === FileType.Actual || f.type === FileType.Preview) + .map(f => { + // Normalize: remove media:// and ensure no double slashes or cross-platform issues + let raw = f.url.replace('media://', '') + return path.normalize(raw) + }) + console.log(`[THUMBNAIL] Iteration detected. Found ${previousFiles.length} files from message ${refMsg.id}:`, previousFiles) + } else { + console.warn(`[THUMBNAIL] Iteration check: No files found in ref message ${refMsg?.id || context.editRefId}`) + } + } + + const adapter = GeminiAdapter.create() + + // 2. Identify all reference images + // a. From previous results (if iteration) + // b. From supply controller (which manages auto-select vs user-attachments) + + const selectedFromSupply = data.selectedReferenceImages || [] + + context.updateStatus(`Preparing reference material: ${selectedFromSupply.length} images...`) + + // 2. Generate the Thumbnail Asset + context.updateStatus('Generating thumbnail image with Gemini...') + const currentModelSettings = (await import('../../settings')).settingsManager.getModelSettings() + const modelName = currentModelSettings.selection['thumbnail'] + + const resultPath = path.join(context.tempDir, THREAD_DIRS.GENERATED_IMAGES, `thumbnail_${context.messageId}.png`) + context.updateStatus(`Generating thumbnail image with Gemini...`) + + const systemInstruction = `You are a professional video thumbnail designer. +Your goal is to create a high-impact, cinematic thumbnail for a video based on a user's request and provided reference frames. + +CRITICAL RULES: +1. VISUAL CONSISTENCY: Maintain a consistent appearance for the subjects, objects, and locations shown in the reference frames. Avoid generating generic characters if the source images show clear subjects. +2. SOURCE MATERIAL: The provided reference frames are your primary baseline. Your output should look like it was professionally edited from these actual video frames. +3. COMPOSITION: Use principles of good graphic design (Rule of Thirds, leading lines, high contrast) to make the thumbnail "pop". +4. USER CONTEXT: You are editing material provided by the personal user. Focus on creative enhancement rather than autonomous generation. + +When provided with a "previous result" and "original reference frames": +- PRIORITIZE CONSISTENCY with the previous result unless the user request explicitly asks for a change. +- Refer back to "original reference frames" to ensure you haven't drifted away from the actual video content.` + + let multimodalPrompt = `User Request: "${intent.content}"\n\nPlease generate a thumbnail that fulfills this request using the provided reference frames from the video.` + if (isIteration) { + multimodalPrompt = `Refinement Request: "${intent.content}" + +The provided images include: +1. The PREVIOUS thumbnail result (first image). +2. ORIGINAL reference frames from the video. + +Please update the previous result based on the Refinement Request while maintaining strict visual consistency with the original video content.` + } + + // Use a maximum of 5 reference images to keep prompt complexity low + const allReferenceImages = [...previousFiles, ...selectedFromSupply].slice(0, 5) + const { record, text } = await adapter.generateImage(modelName, multimodalPrompt, resultPath, allReferenceImages, systemInstruction, context.signal, { includeThinking: true }) + + // Record usage immediately + await context.recordUsage(record) + + if (context.signal.aborted) return; + + // Determine title/content for the final message + const messageContent = text || intent.content + + // Finish pipeline + // Pass the thumbnail as the main file, and ALL reference images (old and new) in the carousel + const finalPreviews = Array.from(new Set(allReferenceImages)) + .filter(f => f !== resultPath) + .map(f => ({ url: f.startsWith('media://') ? f : `media://${f}`, type: FileType.Preview })) + + console.log(`[THUMBNAIL] Finishing stage. Main result: ${resultPath}. Previews: ${finalPreviews.length}`) + + await context.finish( + messageContent, + undefined, // Not used when files is provided in options + undefined, // timeline + { + resultType: 'thumbnail', + files: [ + { url: resultPath, type: FileType.Actual }, + ...finalPreviews + ] + } + ) +} + +function timeToSeconds(t: string | any): number { + if (typeof t !== 'string') return NaN + + // Remove brackets if any [HH:MM:SS] + const clean = t.replace(/[\[\]]/g, '').trim() + const parts = clean.split(':').map(val => parseFloat(val)) + + if (parts.some(p => isNaN(p))) return NaN + + if (parts.length === 3) { + return parts[0] * 3600 + parts[1] * 60 + parts[2] + } else if (parts.length === 2) { + return parts[0] * 60 + parts[1] + } else if (parts.length === 1) { + return parts[0] + } + return NaN +} diff --git a/src/main/scenedetect/index.ts b/src/main/scenedetect/index.ts index 03f73b9..107a15a 100644 --- a/src/main/scenedetect/index.ts +++ b/src/main/scenedetect/index.ts @@ -10,13 +10,101 @@ const CONTENT_THRESHOLD = 27.0 const CSV_FILENAME = 'scenes.csv' const DURATION_TOLERANCE = 0.1 +let resolvedPathCache: string | null = null + +/** + * Locate the scenedetect binary across common OS-specific paths. + * Returns the command to use (either absolute path or 'scenedetect'). + */ +async function resolveScenedetectPath(): Promise { + if (resolvedPathCache) return resolvedPathCache + + // 1. Try default PATH first + const isAvailable = await new Promise((resolve) => { + execFile('scenedetect', ['version'], (error) => resolve(!error)) + }) + if (isAvailable) { + resolvedPathCache = 'scenedetect' + return 'scenedetect' + } + + const home = tmpdir().split(/[\\/]/).slice(0, 3).join('/') // Rough home dir for cross-platform? No, use os.homedir + const { homedir, platform } = await import('os') + const userHome = homedir() + const isWin = platform() === 'win32' + const isMac = platform() === 'darwin' + + const candidates: string[] = [] + + if (isMac) { + candidates.push( + join(userHome, '.pyenv/shims/scenedetect'), + join(userHome, 'Library/Python/3.9/bin/scenedetect'), // Specific versions the user has + join(userHome, 'Library/Python/3.11/bin/scenedetect'), + join(userHome, 'Library/Python/3.12/bin/scenedetect'), + '/usr/local/bin/scenedetect', + '/opt/homebrew/bin/scenedetect' + ) + // Also check pyenv versions + try { + const pyenvVersionsDir = join(userHome, '.pyenv/versions') + const versions = await fs.readdir(pyenvVersionsDir) + for (const v of versions) { + candidates.push(join(pyenvVersionsDir, v, 'bin/scenedetect')) + } + } catch { /* ignore */ } + } else if (isWin) { + const appData = process.env.APPDATA || join(userHome, 'AppData/Roaming') + const localAppData = process.env.LOCALAPPDATA || join(userHome, 'AppData/Local') + candidates.push( + join(appData, 'Python/Python39/Scripts/scenedetect.exe'), + join(appData, 'Python/Python310/Scripts/scenedetect.exe'), + join(appData, 'Python/Python311/Scripts/scenedetect.exe'), + join(appData, 'Python/Python312/Scripts/scenedetect.exe'), + join(localAppData, 'Programs/Python/Python39/Scripts/scenedetect.exe'), + join(localAppData, 'Programs/Python/Python311/Scripts/scenedetect.exe'), + join(localAppData, 'Programs/Python/Python312/Scripts/scenedetect.exe'), + join(userHome, '.pyenv/pyenv-win/bin/scenedetect.bat') + ) + } else { + // Linux + candidates.push( + join(userHome, '.local/bin/scenedetect'), + join(userHome, '.pyenv/shims/scenedetect'), + '/usr/bin/scenedetect', + '/usr/local/bin/scenedetect' + ) + } + + for (const cand of candidates) { + try { + await fs.access(cand) + resolvedPathCache = cand + return cand + } catch { /* ignore */ } + } + + // Last resort: try python3 -m scenedetect + const isPythonModuleAvailable = await new Promise((resolve) => { + execFile('python3', ['-m', 'scenedetect', 'version'], (error) => resolve(!error)) + }) + if (isPythonModuleAvailable) { + resolvedPathCache = 'PYTHON_MODULE' + return 'PYTHON_MODULE' + } + + return 'scenedetect' // Final fallback, even if it fails later +} + /** - * Checks whether the scenedetect CLI is available on PATH. - * Returns true if the version command succeeds, false otherwise. + * Checks whether the scenedetect CLI is available. */ -export function checkScenedetectAvailability(): Promise { +export async function checkScenedetectAvailability(): Promise { + const pathOrRef = await resolveScenedetectPath() + if (pathOrRef === 'PYTHON_MODULE') return true + return new Promise((resolve) => { - execFile('scenedetect', ['version'], (error) => { + execFile(pathOrRef, ['version'], (error) => { resolve(!error) }) }) @@ -85,11 +173,11 @@ export class SceneDetector { * @returns Array of detected scenes sorted by start time. * @throws If the CLI exits non-zero, the CSV is missing, or values cannot be parsed. */ - async detectScenes(videoPath: string): Promise { + async detectScenes(videoPath: string, signal?: AbortSignal): Promise { const tempDir = await fs.mkdtemp(join(tmpdir(), 'scenedetect-')) try { - await this.runScenedetect(videoPath, tempDir) + await this.runScenedetect(videoPath, tempDir, signal) const csvPath = await this.locateCsvFile(tempDir, videoPath) const csvContent = await fs.readFile(csvPath, 'utf-8') return this.parseCsv(csvContent) @@ -106,9 +194,11 @@ export class SceneDetector { /** * Execute the scenedetect CLI process. */ - private runScenedetect(videoPath: string, outputDir: string): Promise { + private async runScenedetect(videoPath: string, outputDir: string, signal?: AbortSignal): Promise { + const pathOrRef = await resolveScenedetectPath() + return new Promise((resolve, reject) => { - const args = [ + const scenedetectArgs = [ '-i', videoPath, 'detect-content', '-t', String(CONTENT_THRESHOLD), @@ -118,8 +208,22 @@ export class SceneDetector { '--skip-cuts' ] - execFile('scenedetect', args, (error, _stdout, stderr) => { + let cmd: string + let args: string[] + + if (pathOrRef === 'PYTHON_MODULE') { + cmd = 'python3' + args = ['-m', 'scenedetect', ...scenedetectArgs] + } else { + cmd = pathOrRef + args = scenedetectArgs + } + + const child = execFile(cmd, args, (error, _stdout, stderr) => { if (error) { + if (signal?.aborted) { + return reject(new Error('Scene detection aborted by user')) + } const code = error.code ?? (error as any).status ?? 'unknown' const stderrMsg = stderr?.trim() || '(no stderr)' console.error(`scenedetect CLI failed for "${videoPath}":`, stderrMsg) @@ -130,6 +234,13 @@ export class SceneDetector { } resolve() }) + + if (signal) { + signal.addEventListener('abort', () => { + console.log('scenedetect process killed by signal') + child.kill('SIGKILL') + }) + } }) } diff --git a/src/main/settings.ts b/src/main/settings.ts index 69d9540..45621ba 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -1,6 +1,6 @@ import { app } from 'electron' import { join } from 'path' -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs' +import { existsSync, mkdirSync, readFileSync, writeFileSync, realpathSync } from 'fs' import os from 'os' import { ModelSettings } from '../shared/types' import { DEFAULT_MODEL_SETTINGS } from './constants/gemini' @@ -17,10 +17,31 @@ class SettingsManager { private defaultTempDir: string constructor() { - const userDataPath = app.getPath('userData') - this.settingsPath = join(userDataPath, 'settings.json') - this.defaultTempDir = join(os.tmpdir(), 'vgtu-video-summarization') - this.settings = this.loadSettings() + this.defaultTempDir = join(os.tmpdir(), 'FrameFlow') + this.settings = { + tempDir: this.defaultTempDir, + modelSettings: DEFAULT_MODEL_SETTINGS + } + // settingsPath will be set in init() + this.settingsPath = '' + } + + public init() { + try { + const userDataPath = app.getPath('userData') + this.settingsPath = join(userDataPath, 'settings.json') + this.settings = this.loadSettings() + console.log(`[SettingsManager] Initialized with userData: ${userDataPath}`) + } catch (error) { + console.error('[SettingsManager] Failed to initialize:', error) + } + } + + private mergeModelSettings(existing: ModelSettings): ModelSettings { + return { + pricing: { ...DEFAULT_MODEL_SETTINGS.pricing, ...existing.pricing }, + selection: { ...DEFAULT_MODEL_SETTINGS.selection, ...existing.selection } + } } private loadSettings(): Settings { @@ -31,7 +52,7 @@ class SettingsManager { return { tempDir: parsed.tempDir || this.defaultTempDir, geminiApiKey: parsed.geminiApiKey, - modelSettings: parsed.modelSettings || DEFAULT_MODEL_SETTINGS + modelSettings: this.mergeModelSettings(parsed.modelSettings || DEFAULT_MODEL_SETTINGS) } } } catch (error) { @@ -60,6 +81,24 @@ class SettingsManager { return dir } + isTempDirUnsafe(): boolean { + try { + const currentDir = this.getTempDir() + const systemTemp = os.tmpdir() + + // On macOS, /var is often a symlink to /private/var. + // Resolving both paths ensures accurate comparison. + const resolvedCurrent = realpathSync(currentDir) + const resolvedSystem = realpathSync(systemTemp) + + return resolvedCurrent.startsWith(resolvedSystem) + } catch (error) { + console.error('Failed to check if temp dir is unsafe:', error) + // Fallback to simpler check if realpath fails + return this.settings.tempDir.startsWith(os.tmpdir()) + } + } + setTempDir(path: string): void { this.settings.tempDir = path this.saveSettings() @@ -103,7 +142,7 @@ class SettingsManager { } setModelSettings(settings: ModelSettings): void { - this.settings.modelSettings = settings + this.settings.modelSettings = this.mergeModelSettings(settings) this.saveSettings() } diff --git a/src/main/tasks/index.ts b/src/main/tasks/index.ts new file mode 100644 index 0000000..3c7b201 --- /dev/null +++ b/src/main/tasks/index.ts @@ -0,0 +1,284 @@ +import { EventEmitter } from 'events' +import { BrowserWindow, ipcMain } from 'electron' +import { threadManager } from '../threads' +import { BackgroundTask, BackgroundTaskState, Thread } from '../../shared/types' +import { PipelineContext } from '../pipeline' +import * as extraction from '../pipeline/phases/extraction' +import * as imageExtraction from '../pipeline/phases/image-extraction' +import { enrichTranscriptWithScenes, SceneDescription } from '../timeline/enrichment' +import { TranscriptItem } from '../gemini/utils' +import * as ffmpegAdapter from '../ffmpeg' +import fs from 'fs' +import path from 'path' + +class BackgroundTaskManager extends EventEmitter { + private runningTasks = new Set() + + constructor() { + super() + } + + public init() { + ipcMain.handle('get-background-tasks', (_event, threadId) => { + const thread = threadManager.getThread(threadId) + return thread?.backgroundTasks || {} + }) + } + + private getTaskKey(threadId: string, taskId: string) { + return `${threadId}:${taskId}` + } + + public async updateTask(threadId: string, taskId: string, updates: Partial) { + const thread = threadManager.getThread(threadId) + if (!thread) return + + const tasks = thread.backgroundTasks || {} + if (!tasks[taskId]) { + tasks[taskId] = { id: taskId, name: taskId, state: 'pending' as BackgroundTaskState } + } + + tasks[taskId] = { ...tasks[taskId], ...updates } + await threadManager.updateThread(threadId, { backgroundTasks: tasks }) + + // Emit IPC update to all windows + BrowserWindow.getAllWindows().forEach(win => { + win.webContents.send('background-task-update', { + threadId, + taskId, + task: tasks[taskId] + }) + }) + + // internal event for waitForTask + this.emit(`task-update:${threadId}:${taskId}`, tasks[taskId]) + } + + public async waitForTask(threadId: string, taskId: string): Promise { + console.log(`[TASK MANAGER] waitForTask('${taskId}') for thread ${threadId}`) + const thread = threadManager.getThread(threadId) + if (!thread) { + console.log(`[TASK MANAGER] thread not found: ${threadId}`) + return + } + + const task = thread.backgroundTasks?.[taskId] + console.log(`[TASK MANAGER] current task state for '${taskId}': ${task?.state || 'undefined'}`) + + if (task?.state === 'completed') { + console.log(`[TASK MANAGER] task '${taskId}' already completed. Returning.`) + return + } + if (task?.state === 'error') throw new Error(`Task ${taskId} failed: ${task.error}`) + + return new Promise((resolve, reject) => { + console.log(`[TASK MANAGER] waiting for 'task-update:${threadId}:${taskId}' event...`) + const listener = (updatedTask: BackgroundTask) => { + console.log(`[TASK MANAGER] event received: 'task-update:${threadId}:${taskId}', new state: ${updatedTask.state}`) + if (updatedTask.state === 'completed') { + this.removeListener(`task-update:${threadId}:${taskId}`, listener) + resolve() + } else if (updatedTask.state === 'error') { + this.removeListener(`task-update:${threadId}:${taskId}`, listener) + reject(new Error(`Task ${taskId} failed: ${updatedTask.error}`)) + } + } + this.on(`task-update:${threadId}:${taskId}`, listener) + }) + } + + private createMockContext(threadId: string, taskId: string): PipelineContext { + const thread = threadManager.getThread(threadId)! + let intentResult: any = undefined + + return { + threadId, + videoPath: thread.videoPath || '', + tempDir: thread.tempDir, + get preprocessing() { + return threadManager.getThread(threadId)?.preprocessing || {} + }, + messageId: 'bg-task', + context: '', // not really used by preprocessing + baseTimeline: undefined, + get intentResult() { return intentResult }, + set intentResult(val) { intentResult = val }, + updateStatus: async (status: string) => { + this.updateTask(threadId, taskId, { state: 'running', status }) + console.log(`[BG ${taskId}] ${status}`) + }, + recordUsage: async (record) => { + // We don't have a message to attach it to, but we can log it or add to thread totals if needed. + console.log(`[BG ${taskId}] Usage: ${record.usage.totalTokens} tokens, Cost: $${record.cost}`) + }, + savePreprocessing: async (updates) => { + const currentThread = threadManager.getThread(threadId) + if (currentThread) { + await threadManager.updateThread(threadId, { + preprocessing: { + ...(currentThread.preprocessing || {}), + ...updates + } + }) + } + }, + waitForTask: async () => { }, + next: () => { }, + finish: async () => { }, + fail: async (error: string) => { + console.error(`[BG ${taskId}] Task failed: ${error}`) + this.updateTask(threadId, taskId, { state: 'error', error }) + }, + signal: new AbortController().signal + } + } + + private async runTask(threadId: string, taskId: string, name: string, fn: (context: PipelineContext) => Promise) { + const thread = threadManager.getThread(threadId) + if (!thread) return + + // Initialize task if not present + await this.updateTask(threadId, taskId, { name, state: 'pending', error: undefined }) + + const taskKey = this.getTaskKey(threadId, taskId) + if (this.runningTasks.has(taskKey)) return + this.runningTasks.add(taskKey) + + try { + await this.updateTask(threadId, taskId, { state: 'running' }) + const context = this.createMockContext(threadId, taskId) + await fn(context) + await this.updateTask(threadId, taskId, { state: 'completed' }) + } catch (error) { + console.error(`Task ${taskId} failed:`, error) + await this.updateTask(threadId, taskId, { + state: 'error', + error: error instanceof Error ? error.message : String(error) + }) + } finally { + this.runningTasks.delete(taskKey) + } + } + + public async startPreprocessing(threadId: string) { + const thread = threadManager.getThread(threadId) + if (!thread) return + + const run = this.runTask.bind(this, threadId) + + // Fire off independent chains + // Chain 1: Downscale -> Audio -> Transcripts + const processingChain = async () => { + if (!thread.preprocessing.lowResVideoPath) { + await run('downscale', 'Downscaling Video', async (ctx) => { + await extraction.ensureLowResolution({}, ctx) + }) + } else { + await this.updateTask(threadId, 'downscale', { name: 'Downscaling Video', state: 'completed' }) + } + + if (!thread.preprocessing.audioPath) { + await run('audio', 'Extracting Audio', async (ctx) => { + await extraction.convertToAudio({}, ctx) + }) + } else { + await this.updateTask(threadId, 'audio', { name: 'Extracting Audio', state: 'completed' }) + } + + if (!thread.preprocessing.rawTranscriptPath) { + await run('rawTranscript', 'Extracting Raw Transcript', async (ctx) => { + await extraction.extractRawTranscript({}, ctx) + }) + } else { + await this.updateTask(threadId, 'rawTranscript', { name: 'Extracting Raw Transcript', state: 'completed' }) + } + + if (!thread.preprocessing.correctedTranscriptPath) { + await run('correctedTranscript', 'Refining Transcript', async (ctx) => { + await extraction.extractCorrectedTranscript({}, ctx) + }) + } else { + await this.updateTask(threadId, 'correctedTranscript', { name: 'Refining Transcript', state: 'completed' }) + } + } + + // Chain 2: Scene Detection & Descriptions (depends on Downscale but we'll await downscale) + const visualChain = async () => { + await this.waitForTask(threadId, 'downscale').catch(() => { }) + + if (!thread.preprocessing.sceneTimesPath) { + await run('sceneTiming', 'Detecting Scenes', async (ctx) => { + await extraction.extractSceneTiming({}, ctx) + }) + } else { + await this.updateTask(threadId, 'sceneTiming', { name: 'Detecting Scenes', state: 'completed' }) + } + + if (!thread.preprocessing.sceneDescriptionsPath) { + await run('sceneDescriptions', 'Describing Scenes', async (ctx) => { + await extraction.generateSceneDescription({}, ctx) + }) + } else { + await this.updateTask(threadId, 'sceneDescriptions', { name: 'Describing Scenes', state: 'completed' }) + } + } + + // Start chains + processingChain().catch(e => console.error(`[BACKGROUND] processingChain failed for ${threadId}:`, e)) + visualChain().catch(e => console.error(`[BACKGROUND] visualChain failed for ${threadId}:`, e)) + + // Chain 3: Enrichment (Wait for BOTH) + const enrichmentChain = async () => { + // Don't catch here - if dependencies fail, this chain should not proceed + await Promise.all([ + this.waitForTask(threadId, 'correctedTranscript'), + this.waitForTask(threadId, 'sceneDescriptions') + ]) + + const currentThread = threadManager.getThread(threadId) + if (!currentThread) return + + if (!currentThread.preprocessing.enrichedTranscriptPath) { + await run('enrichment', 'Unifying Visuals & Text', async (ctx) => { + const transcriptPath = ctx.preprocessing.correctedTranscriptPath || ctx.preprocessing.rawTranscriptPath + const scenesPath = ctx.preprocessing.sceneDescriptionsPath + + if (transcriptPath && scenesPath && fs.existsSync(transcriptPath) && fs.existsSync(scenesPath)) { + const transcript = JSON.parse(fs.readFileSync(transcriptPath, 'utf-8')) as TranscriptItem[] + const scenes = JSON.parse(fs.readFileSync(scenesPath, 'utf-8')) as SceneDescription[] + const duration = await ffmpegAdapter.getVideoDuration(ctx.videoPath) + + const enriched = enrichTranscriptWithScenes(transcript, scenes, duration) + + const enrichedPath = path.join(ctx.tempDir, 'enriched_transcript.json') + fs.writeFileSync(enrichedPath, JSON.stringify(enriched, null, 2)) + + await ctx.savePreprocessing({ enrichedTranscriptPath: enrichedPath }) + } + }) + } else { + await this.updateTask(threadId, 'enrichment', { name: 'Unifying Visuals & Text', state: 'completed' }) + } + } + + enrichmentChain().catch(e => console.error(`[BACKGROUND] enrichmentChain failed for ${threadId}:`, e)) + } + + public async startImageProcessing(threadId: string) { + const thread = threadManager.getThread(threadId) + if (!thread) return + + const run = this.runTask.bind(this, threadId) + + // Task: Extract data from images + if (!thread.preprocessing.imageTextPath) { + await run('imageExtraction', 'Analyzing Images', async (ctx) => { + await imageExtraction.extractImageData({}, ctx) + }) + } else { + await this.updateTask(threadId, 'imageExtraction', { name: 'Analyzing Images', state: 'completed' }) + } + } +} + +export const backgroundTaskManager = new BackgroundTaskManager() diff --git a/src/main/threads/index.ts b/src/main/threads/index.ts index 9cf3387..6d4cb9a 100644 --- a/src/main/threads/index.ts +++ b/src/main/threads/index.ts @@ -1,10 +1,12 @@ -import { app } from 'electron' +import { app, BrowserWindow } from 'electron' import fs from 'fs' import path from 'path' import { v4 as uuidv4 } from 'uuid' import { MessageRole, FileType } from '@shared/types' -import type { Message, Thread, Usage, UsageRecord } from '@shared/types' +import type { Message, Thread, Usage, UsageRecord, VideoMetadata } from '@shared/types' import { settingsManager } from '../settings' +import { getVideoMetadata } from '../ffmpeg' +import { THREAD_DIRS } from '../constants/paths' // Re-export needed types for consumers (if any, though shared is better) export { MessageRole, FileType } @@ -13,10 +15,20 @@ export type { Message } class ThreadManager { private threadsDir: string + private updateQueues: Map> = new Map() constructor() { - this.threadsDir = path.join(app.getPath('userData'), 'threads') - this.ensureThreadsDir() + this.threadsDir = '' + } + + public init() { + try { + this.threadsDir = path.join(app.getPath('userData'), 'threads') + this.ensureThreadsDir() + console.log(`[ThreadManager] Initialized with threadsDir: ${this.threadsDir}`) + } catch (error) { + console.error('[ThreadManager] Failed to initialize:', error) + } } private ensureThreadsDir() { @@ -44,16 +56,78 @@ class ThreadManager { } // Create a new thread - createThread(videoPath: string, videoName: string): Thread { + async createThread(videoPath: string | undefined, videoName: string, imagePaths?: string[]): Promise { const id = uuidv4() + const tempDir = settingsManager.getThreadTempDir(id) + const type = videoPath ? 'video' : 'image' + + let finalVideoPath = videoPath + if (videoPath) { + const systemTempDir = settingsManager.getTempDir() + + // Strict check: No directories allowed as video source + if (fs.existsSync(videoPath) && fs.statSync(videoPath).isDirectory()) { + throw new Error(`Invalid video source: "${videoPath}" is a directory. Please provide a valid video file.`) + } + + if (videoPath.startsWith(systemTempDir)) { + try { + const videoDir = path.join(tempDir, THREAD_DIRS.VIDEO) + if (!fs.existsSync(videoDir)) fs.mkdirSync(videoDir, { recursive: true }) + + // Sanitize the name for the filesystem + const sanitizedName = videoName.replace(/[^\x00-\x7F]/g, '').replace(/[^a-zA-Z0-9.-]/g, '_').trim() + const targetPath = path.join(videoDir, sanitizedName) + + fs.renameSync(videoPath, targetPath) + finalVideoPath = targetPath + + // Cleanup empty source download folder + const sourceDir = path.dirname(videoPath) + if (sourceDir !== systemTempDir && fs.existsSync(sourceDir) && fs.readdirSync(sourceDir).length === 0) { + fs.rmdirSync(sourceDir) + } + } catch (error) { + console.error(`Failed to move video to thread directory:`, error) + // Fallback to original path if move fails + } + } + } + + const sourceImages: string[] = [] + if (imagePaths && imagePaths.length > 0) { + const imagesDir = path.join(tempDir, THREAD_DIRS.IMAGES) + if (!fs.existsSync(imagesDir)) fs.mkdirSync(imagesDir, { recursive: true }) + + for (const imgPath of imagePaths) { + const destPath = path.join(imagesDir, path.basename(imgPath)) + fs.copyFileSync(imgPath, destPath) + sourceImages.push(destPath) + } + } + + + let videoMetadata: VideoMetadata | undefined + if (finalVideoPath) { + try { + videoMetadata = await getVideoMetadata(finalVideoPath) + } catch (error) { + console.error(`Failed to extract metadata for ${finalVideoPath}:`, error) + } + } + const thread: Thread = { id, title: videoName, - videoPath, - preprocessing: {}, - tempDir: settingsManager.getThreadTempDir(id), + type, + videoPath: finalVideoPath, + preprocessing: { + sourceImages: sourceImages.length > 0 ? sourceImages : undefined + }, + tempDir, messages: [], versionCounter: 0, + videoMetadata, createdAt: Date.now(), updatedAt: Date.now() } @@ -62,16 +136,151 @@ class ThreadManager { return thread } + + // Helper to normalize paths (handles symlinks like /var vs /private/var on macOS) + private normalize(p: string | undefined): string | undefined { + if (!p) return p + try { + if (fs.existsSync(p)) return fs.realpathSync(p) + } catch (e) { + // Fallback to absolute path if realpath fails + } + return path.resolve(p) + } + + // Helper to repair paths in a thread if it was moved + private repairThreadPaths(thread: Thread): Thread { + const currentArtifactDir = settingsManager.getTempDir() + const threadId = thread.id + const expectedTempDir = path.join(currentArtifactDir, threadId) + + // Robust comparison using normalized paths + const normalizedCurrent = this.normalize(thread.tempDir) + const normalizedExpected = this.normalize(expectedTempDir) + + if (normalizedCurrent !== normalizedExpected) { + if (fs.existsSync(expectedTempDir)) { + console.log(`[ThreadManager] Repairing paths for thread ${threadId}: ${thread.tempDir} -> ${expectedTempDir}`) + + const oldRoot = thread.tempDir + const newRoot = expectedTempDir + + const fixPath = (p: string | undefined) => { + if (!p) return p + if (p.startsWith(oldRoot)) { + return p.replace(oldRoot, newRoot) + } + return p + } + + // Update root tempDir + thread.tempDir = newRoot + + // Update video path + thread.videoPath = fixPath(thread.videoPath) + + // Update preprocessing paths + if (thread.preprocessing) { + thread.preprocessing.audioPath = fixPath(thread.preprocessing.audioPath) + thread.preprocessing.lowResVideoPath = fixPath(thread.preprocessing.lowResVideoPath) + thread.preprocessing.transcriptPath = fixPath(thread.preprocessing.transcriptPath) + thread.preprocessing.sceneTimesPath = fixPath(thread.preprocessing.sceneTimesPath) + thread.preprocessing.rawTranscriptPath = fixPath(thread.preprocessing.rawTranscriptPath) + thread.preprocessing.sceneDescriptionsPath = fixPath(thread.preprocessing.sceneDescriptionsPath) + thread.preprocessing.enrichedTranscriptPath = fixPath(thread.preprocessing.enrichedTranscriptPath) + + if (thread.preprocessing.sourceImages) { + thread.preprocessing.sourceImages = thread.preprocessing.sourceImages.map(fixPath) as string[] + } + } + + // Update message file paths + thread.messages = thread.messages.map(msg => { + if (msg.files) { + msg.files = msg.files.map(f => ({ + ...f, + url: fixPath(f.url) || '' + })) + } + if (msg.attachedImages) { + msg.attachedImages = msg.attachedImages.map(fixPath) as string[] + } + return msg + }) + + // Save the repaired thread back to metadata and mirror it + this.saveThread(thread) + } + } + + return thread + } + + // Sync threads between userData and artifact directory + syncWithArtifactDir() { + this.ensureThreadsDir() + const currentArtifactDir = settingsManager.getTempDir() + + if (!fs.existsSync(currentArtifactDir)) return + + const projectFolders = fs.readdirSync(currentArtifactDir).filter(f => { + const fullPath = path.join(currentArtifactDir, f) + return fs.statSync(fullPath).isDirectory() && fs.existsSync(path.join(fullPath, 'thread.json')) + }) + + for (const folder of projectFolders) { + const metadataPath = this.getThreadPath(folder) + const hasMetadata = fs.existsSync(metadataPath) + + if (!hasMetadata) { + console.log(`[ThreadManager] Recovering project metadata for ${folder} from artifact directory...`) + try { + const mirrorPath = path.join(currentArtifactDir, folder, 'thread.json') + const content = fs.readFileSync(mirrorPath, 'utf-8') + const thread = JSON.parse(content) as Thread + + // Force path repair upon recovery to ensure it matches current setup + const repairedThread = this.repairThreadPaths(thread) + this.saveThread(repairedThread) + console.log(`[ThreadManager] Successfully recovered project ${folder}`) + } catch (error) { + console.error(`[ThreadManager] Failed to recover project ${folder}:`, error) + } + } else { + // Even if metadata exists, we trigger a repair check to be safe + try { + const content = fs.readFileSync(metadataPath, 'utf-8') + const thread = JSON.parse(content) as Thread + this.repairThreadPaths(thread) + } catch (e) { + // Ignore parse errors here, getAllThreads will handle them + } + } + } + } + // Get all threads (for the list view) getAllThreads(): Thread[] { + // First, sync with the artifact directory to recover any "lost" projects + this.syncWithArtifactDir() + this.ensureThreadsDir() const files = fs.readdirSync(this.threadsDir).filter(file => file.endsWith('.json')) const threads: Thread[] = [] for (const file of files) { + const filePath = path.join(this.threadsDir, file) try { - const content = fs.readFileSync(path.join(this.threadsDir, file), 'utf-8') - const thread = JSON.parse(content) + const content = fs.readFileSync(filePath, 'utf-8') + let thread = JSON.parse(content) as Thread + + // Try to repair paths if they seem broken/moved + thread = this.repairThreadPaths(thread) + + // Mark as missing if artifacts directory is not found, but DO NOT DELETE metadata. + // This allows the user to repair the path or recover files manually. + thread.missing = !!(thread.tempDir && !fs.existsSync(thread.tempDir)) + threads.push(thread) } catch (error) { console.error(`Failed to parse thread file ${file}:`, error) @@ -98,19 +307,43 @@ class ThreadManager { } } - // Update a thread - updateThread(id: string, updates: Partial): Thread | null { - const thread = this.getThread(id) - if (!thread) return null + // Update a thread atomically + updateThread(id: string, updates: Partial): Promise { + const existingQueue = this.updateQueues.get(id) || Promise.resolve() - const updatedThread = { - ...thread, - ...updates, - updatedAt: Date.now() - } + const nextUpdate = existingQueue.then(async () => { + const thread = this.getThread(id) + if (!thread) return null + + const updatedThread = { + ...thread, + ...updates, + updatedAt: Date.now() + } + + this.saveThread(updatedThread) - this.saveThread(updatedThread) - return updatedThread + // Broadcast update to all renderer windows + BrowserWindow.getAllWindows().forEach(win => { + win.webContents.send('thread-updated', updatedThread) + }) + + return updatedThread + }).catch(err => { + console.error(`Atomic update failed for thread ${id}:`, err) + return null + }) + + this.updateQueues.set(id, nextUpdate) + + // Optional: clean up map if queue is idle (not strictly necessary for small number of threads) + nextUpdate.finally(() => { + if (this.updateQueues.get(id) === nextUpdate) { + // Don't delete if another update was already queued + } + }) + + return nextUpdate } private deleteFile(filePath: string) { @@ -190,36 +423,30 @@ class ThreadManager { } // Helper to add a message to a thread - addMessageToThread(threadId: string, message: Omit): Message | null { - const thread = this.getThread(threadId) - if (!thread) return null - + async addMessageToThread(threadId: string, message: Omit): Promise { + const id = uuidv4() + const createdAt = Date.now() const fullMessage: Message = { - id: uuidv4(), - createdAt: Date.now(), + id, + createdAt, ...message } - thread.messages.push(fullMessage) - thread.updatedAt = Date.now() + await this.updateThread(threadId, { + messages: [...(this.getThread(threadId)?.messages || []), fullMessage] + }) - this.saveThread(thread) return fullMessage } // Update specific message in a thread - updateMessageInThread(threadId: string, messageId: string, updates: Partial): boolean { - const thread = this.getThread(threadId) - if (!thread) return false - - const msgIndex = thread.messages.findIndex(m => m.id === messageId) - if (msgIndex === -1) return false - - thread.messages[msgIndex] = { ...thread.messages[msgIndex], ...updates } - thread.updatedAt = Date.now() - - this.saveThread(thread) - return true + async updateMessageInThread(threadId: string, messageId: string, updates: Partial): Promise { + const result = await this.updateThread(threadId, { + messages: (this.getThread(threadId)?.messages || []).map(m => + m.id === messageId ? { ...m, ...updates } : m + ) + }) + return !!result } getNextVersion(threadId: string): number { @@ -232,32 +459,33 @@ class ThreadManager { } // Update usage and cost for a message - updateMessageUsage(threadId: string, messageId: string, record: UsageRecord): boolean { - const thread = this.getThread(threadId) - if (!thread) return false - - const msgIndex = thread.messages.findIndex(m => m.id === messageId) - if (msgIndex === -1) return false - - const currentMsg = thread.messages[msgIndex] - const newUsage: Usage = { - promptTokens: (currentMsg.usage?.promptTokens || 0) + record.usage.promptTokens, - candidatesTokens: (currentMsg.usage?.candidatesTokens || 0) + record.usage.candidatesTokens, - thinkingTokens: (currentMsg.usage?.thinkingTokens || 0) + (record.usage.thinkingTokens || 0), - totalTokens: (currentMsg.usage?.totalTokens || 0) + record.usage.totalTokens - } - - const newCost = (currentMsg.cost || 0) + record.cost + async updateMessageUsage(threadId: string, messageId: string, record: UsageRecord): Promise { + const result = await this.updateThread(threadId, { + messages: (this.getThread(threadId)?.messages || []).map(m => { + if (m.id !== messageId) return m + + const newUsage: Usage = { + promptTokens: (m.usage?.promptTokens || 0) + record.usage.promptTokens, + candidatesTokens: (m.usage?.candidatesTokens || 0) + record.usage.candidatesTokens, + thinkingTokens: (m.usage?.thinkingTokens || 0) + (record.usage.thinkingTokens || 0), + totalTokens: (m.usage?.totalTokens || 0) + record.usage.totalTokens + } - thread.messages[msgIndex] = { - ...currentMsg, - usage: newUsage, - cost: newCost - } + return { + ...m, + usage: newUsage, + cost: (m.cost || 0) + record.cost + } + }) + }) + return !!result + } - thread.updatedAt = Date.now() - this.saveThread(thread) - return true + async updateThreadNodePositions(threadId: string, positions: Record): Promise { + const result = await this.updateThread(threadId, { + nodePositions: positions + }) + return !!result } // Reset all pending messages to non-pending (e.g. on app startup or shutdown) resetPendingMessages(): void { @@ -293,6 +521,50 @@ class ThreadManager { }).join('\n\n') } + // Traverse the node tree backwards via editRefId as the parent link to form an isolated branch context + getBranchContext(threadId: string, messageId?: string): { text: string, attachedImages: string[] } { + const thread = this.getThread(threadId) + if (!thread) return { text: '', attachedImages: [] } + + if (!messageId) { + const text = this.getThreadContext(threadId) + // For full thread context, collect ALL attached images from ALL messages + const attachedImages = Array.from(new Set( + thread.messages.flatMap(m => m.attachedImages || []) + )) + return { text, attachedImages } + } + + const branchMessages: Message[] = [] + let currentMsgId: string | undefined = messageId + + while (currentMsgId) { + const msg = thread.messages.find(m => m.id === currentMsgId) + if (!msg) break + + branchMessages.push(msg) + currentMsgId = msg.editRefId + } + + // Array is constructed backward (leaf to root), reverse it chronologically + branchMessages.reverse() + + const text = branchMessages.map(m => { + let content = `${m.role.toUpperCase()}: ${m.content}` + if (m.timeline && m.timeline.length > 0) { + const timelineText = m.timeline.map(t => `[${t.start} - ${t.end}] ${t.text}`).join('\n') + content += `\n(AI Generated Timeline):\n${timelineText}` + } + return content + }).join('\n\n') + + const attachedImages = Array.from(new Set( + branchMessages.flatMap(m => m.attachedImages || []) + )) + + return { text, attachedImages } + } + // Get the last user message in a thread getLatestUserMessage(threadId: string): Message | null { const thread = this.getThread(threadId) @@ -300,31 +572,48 @@ class ThreadManager { return [...thread.messages].reverse().find(m => m.role === MessageRole.User) || null } - // Remove a message from a thread and delete its files - removeMessageFromThread(threadId: string, messageId: string): boolean { + // Remove a message from a thread and all its descendants recursively + async removeMessageBranchFromThread(threadId: string, messageId: string): Promise { const thread = this.getThread(threadId) if (!thread) return false - const msgIndex = thread.messages.findIndex(m => m.id === messageId) - if (msgIndex === -1) return false + const toRemove = new Set() + const collect = (id: string) => { + if (toRemove.has(id)) return + toRemove.add(id) + const children = thread.messages.filter(m => m.editRefId === id) + children.forEach(c => collect(c.id)) + } - const msg = thread.messages[msgIndex] + collect(messageId) - // Delete associated files (except original) - if (msg.files) { - for (const file of msg.files) { - if (file.type !== FileType.Original) { - this.deleteFile(file.url) + // Delete associated files for all messages in the branch (except original and protected reference/analysis files) + for (const id of toRemove) { + const msg = thread.messages.find(m => m.id === id) + if (msg && msg.files) { + for (const file of msg.files) { + if (file.type !== FileType.Original) { + const cleanPath = file.url.replace('file://', '').replace('media://', '') + + // Only allow deletion if it belongs to generated directories + const isGenerated = cleanPath.includes(`/${THREAD_DIRS.GENERATED_IMAGES}/`) || + cleanPath.includes(`/${THREAD_DIRS.GENERATED_VIDEOS}/`) + + if (isGenerated) { + this.deleteFile(file.url) + } else { + console.log(`[ThreadManager] Skipping deletion of reference/analysis file during node removal: ${cleanPath}`) + } + } } } } - // Remove from messages array - thread.messages.splice(msgIndex, 1) - thread.updatedAt = Date.now() + const result = await this.updateThread(threadId, { + messages: thread.messages.filter(m => !toRemove.has(m.id)) + }) - this.saveThread(thread) - return true + return !!result } } diff --git a/src/main/timeline/enrichment.ts b/src/main/timeline/enrichment.ts index 674e238..d62d42a 100644 --- a/src/main/timeline/enrichment.ts +++ b/src/main/timeline/enrichment.ts @@ -1,4 +1,5 @@ import { TranscriptItem } from '../gemini/utils' +import { EnrichedTimelineSegment } from '../../shared/types' export interface SceneDescription { index: number @@ -6,26 +7,24 @@ export interface SceneDescription { description: string } -const MIN_GAP_DURATION = 2.0 // Minimum 2 seconds gap to insert a visual scene +const MIN_GAP_DURATION = 0.5 // Minimum gap to insert a dedicated visual-only segment +const MAX_GAP_CHUNK_DURATION = 15.0 // Maximum duration for a single silence segment /** - * Enriches the transcript by filling gaps with visual scene descriptions. + * Enriches the transcript by merging visual scene descriptions into every segment. + * Ensures that even audio segments have visual context, and gaps are filled with visual-only segments. */ export function enrichTranscriptWithScenes( transcript: TranscriptItem[], sceneDescriptions: SceneDescription[], totalDuration: number -): TranscriptItem[] { - // 1. Convert transcript times to seconds for easier processing +): EnrichedTimelineSegment[] { + // 1. Convert transcript times to seconds and sort const sortedTranscript = [...transcript].sort((a, b) => timeToSeconds(a.start) - timeToSeconds(b.start)) - const enrichedItems: TranscriptItem[] = [] + const enrichedItems: EnrichedTimelineSegment[] = [] // 2. Helper to find relevant scene description for a timestamp - const getSceneForTime = (time: number): string | null => { - // Find the scene that started most recently before this time - // or assumes scenes are roughly sequential - // sceneDescriptions usually has index, startTime. We assume scenes cover the video. - // We find the scene with largest startTime <= time + const getSceneForTime = (time: number): string => { let bestScene: SceneDescription | null = null for (const scene of sceneDescriptions) { if (scene.startTime <= time) { @@ -34,56 +33,64 @@ export function enrichTranscriptWithScenes( } } } - return bestScene ? bestScene.description : null + return bestScene ? bestScene.description : "No visual description available." } - // 3. Iterate transcript to find gaps + // Helper to add split gap segments + const addGapSegments = (gapStart: number, gapEnd: number) => { + let currentStart = gapStart + while (currentStart < gapEnd) { + const currentEnd = Math.min(currentStart + MAX_GAP_CHUNK_DURATION, gapEnd) + const midPoint = currentStart + (currentEnd - currentStart) / 2 + + enrichedItems.push({ + index: currentIndex++, + start: secondsToTime(currentStart), + end: secondsToTime(currentEnd), + duration: currentEnd - currentStart, + text: "[Silence]", + visual: getSceneForTime(midPoint) + }) + currentStart = currentEnd + } + } + + // 3. Iterate transcript and merge with scenes let previousEnd = 0.0 + let currentIndex = 1 for (const item of sortedTranscript) { const start = timeToSeconds(item.start) const end = timeToSeconds(item.end) - // Check for gap + // Check for significant gap before this transcript item if (start - previousEnd > MIN_GAP_DURATION) { - const gapStart = previousEnd - const gapEnd = start - const gapDuration = gapEnd - gapStart - const midPoint = gapStart + (gapDuration / 2) - - const visualDescription = getSceneForTime(midPoint) - if (visualDescription) { - enrichedItems.push({ - start: secondsToTime(gapStart), - end: secondsToTime(gapEnd), - text: `[Visual Scene: ${visualDescription}]` - }) - } + addGapSegments(previousEnd, start) } - enrichedItems.push(item) + // Process the transcript item + const itemMidPoint = start + (end - start) / 2 + enrichedItems.push({ + index: currentIndex++, + start: item.start, + end: item.end, + duration: end - start, + text: item.text, + visual: getSceneForTime(itemMidPoint) + }) + previousEnd = end } // 4. Check for trailing gap if (totalDuration - previousEnd > MIN_GAP_DURATION) { - const gapStart = previousEnd - const gapEnd = totalDuration - const midPoint = gapStart + ((gapEnd - gapStart) / 2) - - const visualDescription = getSceneForTime(midPoint) - if (visualDescription) { - enrichedItems.push({ - start: secondsToTime(gapStart), - end: secondsToTime(gapEnd), - text: `[Visual Scene: ${visualDescription}]` - }) - } + addGapSegments(previousEnd, totalDuration) } return enrichedItems } + // Helpers function timeToSeconds(t: string): number { const clean = t.trim().replace(',', '.') diff --git a/src/main/timeline/index.ts b/src/main/timeline/index.ts index 057d39d..4684c26 100644 --- a/src/main/timeline/index.ts +++ b/src/main/timeline/index.ts @@ -1,19 +1,21 @@ -import { TimelineSegment, UsageRecord } from '../../shared/types' -import { TranscriptItem, generateSRT } from '../gemini/utils' +import { TimelineSegment, EnrichedTimelineSegment, UsageRecord } from '../../shared/types' +import { TranscriptItem } from '../gemini/utils' import { GeminiAdapter } from '../gemini/adapter' export interface GenerateTimelineOptions { userExpectation: string; - allSegments: TranscriptItem[]; + allSegments: EnrichedTimelineSegment[]; targetDuration: number; - baseTimeline?: TimelineSegment[]; + baseTimeline?: EnrichedTimelineSegment[]; modelName: string; mode: 'new' | 'edit'; onUpdateStatus?: (status: string) => void; onRecordUsage?: (record: UsageRecord) => void; + signal?: AbortSignal; } -export async function generateTimeline(options: GenerateTimelineOptions): Promise { + +export async function generateTimeline(options: GenerateTimelineOptions): Promise { const { userExpectation, allSegments, @@ -22,60 +24,67 @@ export async function generateTimeline(options: GenerateTimelineOptions): Promis onRecordUsage, baseTimeline = [], modelName, - mode + mode, + signal } = options; - const fullTimelineSRT = generateSRT(allSegments); + const fullTranscriptText = formatEnrichedTranscript(allSegments); const geminiAdapter = GeminiAdapter.create(); + if (mode === 'edit') { return editTimeline({ userExpectation, allSegments, - fullTimelineSRT, + fullTranscriptText, targetDuration, baseTimeline, geminiAdapter, modelName, onUpdateStatus, - onRecordUsage + onRecordUsage, + signal }); } else { return generateNewTimeline({ userExpectation, allSegments, - fullTimelineSRT, + fullTranscriptText, targetDuration, geminiAdapter, modelName, onUpdateStatus, onRecordUsage, - baseTimeline + baseTimeline, + signal }); } } async function editTimeline(options: { userExpectation: string; - allSegments: TranscriptItem[]; - fullTimelineSRT: string; + allSegments: EnrichedTimelineSegment[]; + fullTranscriptText: string; targetDuration: number; - baseTimeline: TimelineSegment[]; + baseTimeline: EnrichedTimelineSegment[]; geminiAdapter: GeminiAdapter; modelName: string; onUpdateStatus?: (status: string) => void; onRecordUsage?: (record: UsageRecord) => void; -}): Promise { + signal?: AbortSignal; +}): Promise { + const { userExpectation, allSegments, - fullTimelineSRT, + fullTranscriptText, targetDuration, baseTimeline, geminiAdapter, modelName, onUpdateStatus, - onRecordUsage + onRecordUsage, + signal } = options; onUpdateStatus?.(`Editing timeline in one-shot...`); @@ -87,20 +96,27 @@ async function editTimeline(options: { const systemInstruction = ` You are a video editor assistant. Your task is to EDIT an existing video timeline based on the user's technical request. -You are provided with the FULL transcript (SRT) and the CURRENT timeline. +You are provided with the FULL transcript and the CURRENT timeline. + +The transcript includes BOTH visual descriptions and audio captions for each segment. +Each segment is exactly one index (starting from 1). -The transcript includes [Visual Scene] and [Audio] segments. Use them if they help fulfill the request (e.g. "show more action" -> pick visual scenes). +Format per segment: +INDEX: [START - END] | Visual: Scene description | Audio: Transcript text Strict Rules for Editing: 1. MAXIMAL CONSISTENCY: Do NOT change segments from the CURRENT timeline unless the user's request explicitly requires it. 2. PRESERVE ORDER: Maintain the existing sequence of segments as much as possible. 3. MINIMAL CHANGES: If a user asks to add something, keep the rest of the timeline identical. If they ask to remove something, only remove that specific part. 4. ONE-SHOT RESULT: Return the COMPLETE list of indices for the NEW version of the timeline. -5. RESPECT DURATION: The total duration of the NEW timeline must be close to the Target Duration if specified. If the user asks for a specific length (e.g., "30 seconds"), ensure the selected segments' durations sum up to approximately that value. +5. RESPECT DURATION: The total duration of the NEW timeline must be close to the Target Duration if specified. +6. VISUAL CONTEXT: Use the "Visual" part to understand the scene content even if the "Audio" is [Silence]. +7. AVOID HUGE SEGMENTS: If a single segment is longer than 30s and doesn't contain essential audio, avoid picking it unless necessary. Return ONLY a JSON array of indices (integers) of the segments that should make up the NEW timeline, e.g. [1, 5, 8]. -Indices must refer to the indices in the FULL transcript (SRT). +Indices refer to the line numbers (starting at 1) of the FULL transcript. Do not include any other text. + `; const prompt = ` @@ -108,8 +124,8 @@ User Editing Request: ${userExpectation} Target Duration: ${targetDuration} seconds ----------------- -FULL transcript (SRT): -${fullTimelineSRT} +FULL transcript: +${fullTranscriptText} ----------------- CURRENT timeline: @@ -120,14 +136,14 @@ Task: Provide a list of indices representing the new timeline after applying the `; try { - const { text: responseText, record } = await geminiAdapter.generateText(modelName, prompt, systemInstruction); + const { text: responseText, record } = await geminiAdapter.generateText(modelName, prompt, systemInstruction, signal); console.log(`Gemini response (Edit Mode):`, responseText); // Record usage for the edit call onRecordUsage?.(record); const indices = parseIndicesFromResponse(responseText); - const newTimeline: TimelineSegment[] = []; + const newTimeline: EnrichedTimelineSegment[] = []; for (const idx of indices) { const segmentIndex = idx - 1; @@ -139,6 +155,7 @@ Task: Provide a list of indices representing the new timeline after applying the start: originalSegment.start, end: originalSegment.end, text: originalSegment.text, + visual: originalSegment.visual, duration: duration }); } @@ -153,28 +170,31 @@ Task: Provide a list of indices representing the new timeline after applying the async function generateNewTimeline(options: { userExpectation: string; - allSegments: TranscriptItem[]; - fullTimelineSRT: string; + allSegments: EnrichedTimelineSegment[]; + fullTranscriptText: string; targetDuration: number; geminiAdapter: GeminiAdapter; modelName: string; onUpdateStatus?: (status: string) => void; onRecordUsage?: (record: UsageRecord) => void; - baseTimeline?: TimelineSegment[]; -}): Promise { + baseTimeline?: EnrichedTimelineSegment[]; + signal?: AbortSignal; +}): Promise { + const { userExpectation, allSegments, - fullTimelineSRT, + fullTranscriptText, targetDuration, geminiAdapter, modelName, onUpdateStatus, onRecordUsage, - baseTimeline = [] + baseTimeline = [], + signal } = options; - const currentShorterTimeline: TimelineSegment[] = [...baseTimeline]; + const currentShorterTimeline: EnrichedTimelineSegment[] = [...baseTimeline]; let currentDuration = calculateTotalDuration(currentShorterTimeline); let iterationCount = 0; @@ -184,20 +204,29 @@ async function generateNewTimeline(options: { You are a video editor assistant. Your task is to select the next best segments from the full transcript to build a shorter video timeline based on the user's request. -You will see segments that are spoken text, and segments in brackets like [Visual Scene: ...] or [Music]. -- [Visual Scene] segments describe what is happening visually when no one is speaking. USE THESE to show relevant actions or set the scene. -- [Music] or [Applause] can be used for impact or transitions. +The transcript includes BOTH visual descriptions and audio captions for each segment. +Each segment is exactly one index (starting from 1). + +Format per segment: +INDEX: [START - END] | Visual: Scene description | Audio: Transcript text IMPORTANT: Selection Logic 1. CHRONOLOGICAL ORDER: Unless the user explicitly asks for a non-linear narrative (e.g. "start with the ending", "flashback"), you MUST select segments that appear later in the timeline than the previous ones. 2. GRADUAL PROGRESSION: The story line should be kept based on timing, and do not mix times. Ideally select the next best segment that continues the flow naturally. - - Example: If the timeline has a segment at 00:10:10, do NOT select a segment at 00:08:05. The time must always move forward. +3. VISUAL CONTEXT: Use the "Visual" part to understand the scene content even if the "Audio" is [Silence]. Use these segments to bridge gaps or set the scene. +4. AVOID HUGE SEGMENTS: Do NOT pick a single segment that is longer than the Target Duration or consumes most of it (e.g. >30s) unless strictly required by the user's request. Return ONLY a JSON array of indices (integers) of the selected segments, e.g. [1, 5, 8]. +Indices refer to the line numbers (starting at 1) of the FULL transcript. Do not include any other text. + `; while (currentDuration < targetDuration && iterationCount < MAX_ITERATIONS) { + if (signal?.aborted) { + console.log('[TIMELINE] Aborting generation loop - signal triggered.') + throw new Error('Timeline generation aborted by user') + } iterationCount++; onUpdateStatus?.(`Iteration ${iterationCount} - Duration: ${currentDuration.toFixed(1)}s / ${targetDuration}s`); @@ -210,8 +239,8 @@ User Request: ${userExpectation} Target Duration: ${targetDuration} seconds ----------------- -Full transcript (SRT): -${fullTimelineSRT} +Full transcript: +${fullTranscriptText} ----------------- Current built timeline (${currentDuration.toFixed(1)}s): @@ -222,12 +251,13 @@ Task: Pick the next 3 segments to add to the timeline. `; try { - const { text: responseText, record } = await geminiAdapter.generateText(modelName, prompt, systemInstruction); - console.log(`Gemini response (Iteration ${iterationCount}):`, responseText); - - // Record usage for each iteration + const { text: responseText, record } = await geminiAdapter.generateText(modelName, prompt, systemInstruction, signal); + + // Record usage IMMEDIATELY after call finishes, before any abort checks onRecordUsage?.(record); + console.log(`Gemini response (Iteration ${iterationCount}):`, responseText); + const indices = parseIndicesFromResponse(responseText); if (indices.length === 0) { @@ -249,6 +279,7 @@ Task: Pick the next 3 segments to add to the timeline. start: originalSegment.start, end: originalSegment.end, text: originalSegment.text, + visual: originalSegment.visual, duration: duration }); addedCount++; @@ -264,6 +295,7 @@ Task: Pick the next 3 segments to add to the timeline. currentDuration = calculateTotalDuration(currentShorterTimeline); } catch (error) { + if (signal?.aborted) throw new Error('Timeline generation aborted by user') console.error("Error in generateNewTimeline iteration:", error); onUpdateStatus?.(`Error in AI generation: ${error instanceof Error ? error.message : String(error)}`); break; @@ -298,6 +330,14 @@ function calculateTotalDuration(segments: TimelineSegment[]): number { return segments.reduce((acc, curr) => acc + curr.duration, 0); } +function formatEnrichedTranscript(items: EnrichedTimelineSegment[]): string { + return items.map((item) => { + const duration = item.duration.toFixed(1); + return `${item.index}: [${item.start} - ${item.end}] (${duration}s) | Visual: ${item.visual} | Audio: ${item.text}` + }).join('\n') +} + + function parseIndicesFromResponse(text: string): number[] { const jsonMatch = text.match(/\[([\d,\s]+)\]/); if (jsonMatch) { diff --git a/src/main/ytdlp.ts b/src/main/ytdlp.ts new file mode 100644 index 0000000..fa5540b --- /dev/null +++ b/src/main/ytdlp.ts @@ -0,0 +1,168 @@ +import path, { join } from 'path' +import { YtDlp } from 'ytdlp-nodejs' +import fs from 'fs' +import { getFFmpegBinaryPath } from './ffmpeg' + +const ytdlp = new YtDlp() + +export async function checkYtDlpAvailability(): Promise { + try { + const isInstalled = await ytdlp.checkInstallationAsync() + if (!isInstalled) { + // Try to update/download if not present + await ytdlp.updateYtDlpAsync() + return await ytdlp.checkInstallationAsync() + } + return true + } catch (error) { + console.error('Failed to check or install yt-dlp:', error) + return false + } +} + +export async function getVideoFormats(url: string): Promise { + try { + const info = await ytdlp.getInfoAsync(url) + if (info._type !== 'video') { + throw new Error('URL is not a single video (possible playlist or channel)') + } + const formats = info.formats || [] + const heights = new Set() + + formats.forEach((f: any) => { + if (f.height && f.height >= 144) { + heights.add(f.height) + } + }) + + // Sort descending: 1080, 720, 480... + const sortedHeights = Array.from(heights) + .sort((a, b) => b - a) + .map(h => h.toString()) + + return sortedHeights.length > 0 ? sortedHeights : ['Best'] + } catch (e: any) { + console.error('Failed to fetch formats:', e) + const message = e.message || 'Unknown error' + // Specialize message for better UI feedback + if (message.includes('Unsupported URL')) { + throw new Error('Unsupported URL: yt-dlp does not support this website.') + } + throw new Error(`Failed to fetch video formats: ${message}`) + } +} + +export async function downloadVideo( + url: string, + tempFolder: string, + resolution?: string, + onProgress?: (percent: number) => void +): Promise<{ path: string; name: string }> { + // Output template: tempFolder / videoTitle.ext + const outputTemplate = join(tempFolder, '%(title)s.%(ext)s') + + const builder = ytdlp.download(url) + .output(outputTemplate) + .addArgs('--no-playlist') + .addArgs('--ffmpeg-location', getFFmpegBinaryPath()) + + if (resolution && resolution !== 'Best') { + // quality() logic + const formatStr = `bestvideo[height<=${resolution}][ext=mp4]+bestaudio[ext=m4a]/best[height<=${resolution}][ext=mp4]/best` + builder.addArgs('-f', formatStr) + } else { + const formatStr = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best' + builder.addArgs('-f', formatStr) + } + + if (onProgress) { + builder.on('progress', (p) => { + if (p.percentage !== undefined) { + onProgress(p.percentage) + } + }) + } + + try { + const result = await builder.run() + + let downloadedPath = result.filePaths?.[0] + + // 0. Safety: Check if we got anything + if (!downloadedPath || !fs.existsSync(downloadedPath)) { + // Fallback: scan the temp directory + const files = fs.readdirSync(tempFolder) + if (files.length > 0) { + downloadedPath = join(tempFolder, files[0]) + } + } + + if (!downloadedPath || !fs.existsSync(downloadedPath)) { + throw new Error('No files found in temp folder after download') + } + + // 1. Resolve 'Is a directory' issues + // If yt-dlp created a directory (e.g. for folder link or failed merge), find the biggest file or merged file + if (fs.statSync(downloadedPath).isDirectory()) { + console.log(`[YTDLP] Download result is a directory: ${downloadedPath}. Resolving to flat file...`) + const files = fs.readdirSync(downloadedPath) + .map(f => ({ name: f, path: join(downloadedPath!, f), size: fs.statSync(join(downloadedPath!, f)).size })) + .filter(f => !fs.statSync(f.path).isDirectory()) + .sort((a, b) => b.size - a.size) // Pick biggest file (likely the video) + + if (files.length === 0) { + throw new Error('yt-dlp download failed: Result was an empty directory') + } + + const bestFile = files[0] + const newPath = join(tempFolder, bestFile.name) + fs.renameSync(bestFile.path, newPath) + + // Cleanup the directory if it's now empty or just temp fragments + try { fs.rmSync(downloadedPath, { recursive: true, force: true }) } catch (e) {} + + downloadedPath = newPath + } + + // 2. Normalize filename + const dir = path.dirname(downloadedPath) + const originalFileName = path.basename(downloadedPath) + + // Robust normalization: strip redundant extensions + const ext = path.extname(originalFileName) + let base = path.basename(originalFileName, ext) + + // Keep stripping common video extensions from the end of the base name + const videoExtensions = ['.mp4', '.m4a', '.webm', '.mov', '.mkv', '.avi', '.f136', '.f140', '.f251'] + while (true) { + const subExt = path.extname(base) + if (!subExt) break + if (videoExtensions.includes(subExt.toLowerCase()) || subExt.match(/^\.f\d+$/)) { + base = path.basename(base, subExt) + } else { + break + } + } + + const newFileName = `${base.trim()}${ext}` + let finalPath = downloadedPath + let finalName = originalFileName + + if (newFileName !== originalFileName) { + const newPath = join(dir, newFileName) + if (!fs.existsSync(newPath)) { + fs.renameSync(downloadedPath, newPath) + finalPath = newPath + finalName = newFileName + } + } + + return { path: finalPath, name: finalName } + } catch (error: any) { + console.error('Download failed:', error) + throw new Error(`yt-dlp download failed: ${error.message}`) + } +} + + + diff --git a/src/preload/index.ts b/src/preload/index.ts index a7c7e71..42c4153 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -2,38 +2,66 @@ import { contextBridge, ipcRenderer } from 'electron' const api = { selectVideo: () => ipcRenderer.invoke('select-video'), + fetchVideoFormats: (url: string) => ipcRenderer.invoke('fetch-video-formats', url), + downloadVideo: (url: string, resolution?: string) => ipcRenderer.invoke('download-video', url, resolution), + onDownloadProgress: (callback: (percent: number) => void) => { + const listener = (_event: any, percent: number) => callback(percent) + ipcRenderer.on('download-progress', listener) + return () => ipcRenderer.removeListener('download-progress', listener) + }, checkSystemRequirements: () => ipcRenderer.invoke('check-system-requirements'), startPipeline: (data: { threadId: string; userPromptMessageId: string; newAiMessageId: string; editReferenceMessageId?: string }) => ipcRenderer.invoke('start-pipeline', data), + abortPipeline: (messageId: string) => ipcRenderer.invoke('abort-pipeline', messageId), onPipelineUpdate: (callback: (data: any) => void) => { const listener = (_event: any, data: any) => callback(data) ipcRenderer.on('pipeline-update', listener) return () => ipcRenderer.removeListener('pipeline-update', listener) }, + getBackgroundTasks: (threadId: string) => ipcRenderer.invoke('get-background-tasks', threadId), + retryPreprocessing: (threadId: string) => ipcRenderer.invoke('retry-preprocessing', threadId), + debugLog: (...args: any[]) => ipcRenderer.invoke('debug-log', ...args), + onBackgroundTaskUpdate: (callback: (data: any) => void) => { + const listener = (_event: any, data: any) => callback(data) + ipcRenderer.on('background-task-update', listener) + return () => ipcRenderer.removeListener('background-task-update', listener) + }, + onThreadUpdated: (callback: (thread: any) => void) => { + const listener = (_event: any, thread: any) => callback(thread) + ipcRenderer.on('thread-updated', listener) + return () => ipcRenderer.removeListener('thread-updated', listener) + }, getTempDir: () => ipcRenderer.invoke('get-temp-dir'), setTempDir: () => ipcRenderer.invoke('set-temp-dir'), resetTempDir: () => ipcRenderer.invoke('reset-temp-dir'), openTempDir: () => ipcRenderer.invoke('open-temp-dir'), getGeminiApiKey: () => ipcRenderer.invoke('get-gemini-api-key'), + isTempDirUnsafe: () => ipcRenderer.invoke('is-temp-dir-unsafe'), setGeminiApiKey: (key: string) => ipcRenderer.invoke('set-gemini-api-key', key), getModelSettings: () => ipcRenderer.invoke('get-model-settings'), setModelSettings: (settings: any) => ipcRenderer.invoke('set-model-settings', settings), resetModelSettings: () => ipcRenderer.invoke('reset-model-settings'), // Thread Management - createThread: (videoPath: string, videoName: string) => - ipcRenderer.invoke('create-thread', { videoPath, videoName }), + createThread: (data: { videoPath?: string, videoName: string, imagePaths?: string[] }) => + ipcRenderer.invoke('create-thread', data), getAllThreads: () => ipcRenderer.invoke('get-all-threads'), getThread: (id: string) => ipcRenderer.invoke('get-thread', id), deleteThread: (id: string) => ipcRenderer.invoke('delete-thread', id), deleteAllThreads: () => ipcRenderer.invoke('delete-all-threads'), addMessage: (threadId: string, message: any) => ipcRenderer.invoke('add-message', { threadId, message }), + saveNodePositions: (threadId: string, positions: Record) => + ipcRenderer.invoke('save-node-positions', { threadId, positions }), removeMessage: (threadId: string, messageId: string) => ipcRenderer.invoke('remove-message', { threadId, messageId }), showConfirmation: (options: { title: string, message: string, detail?: string, type?: string, buttons?: string[], defaultId?: number, cancelId?: number }) => ipcRenderer.invoke('show-confirmation', options), saveVideo: (sourcePath: string) => ipcRenderer.invoke('save-video', sourcePath), - openThreadDir: (threadId: string) => ipcRenderer.invoke('open-thread-dir', threadId) + openThreadDir: (threadId: string) => ipcRenderer.invoke('open-thread-dir', threadId), + getVideoMetadata: (filePath: string) => ipcRenderer.invoke('get-video-metadata', filePath), + showOpenDialog: (options: any) => ipcRenderer.invoke('show-open-dialog', options), + upscaleImage: (data: { threadId: string, messageId: string, imagePath: string, upscaleFactor: string }) => + ipcRenderer.invoke('upscale-image', data) } if (process.contextIsolated) { diff --git a/src/renderer/index.html b/src/renderer/index.html index 67bfaf3..949712a 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -2,7 +2,7 @@ - AI Video Summarizer + FrameFlow diff --git a/src/renderer/src/App.vue b/src/renderer/src/App.vue index 444656d..23d2907 100644 --- a/src/renderer/src/App.vue +++ b/src/renderer/src/App.vue @@ -3,9 +3,6 @@
- -
-
diff --git a/src/renderer/src/assets/main.css b/src/renderer/src/assets/main.css index c6c88c3..c6d6c56 100644 --- a/src/renderer/src/assets/main.css +++ b/src/renderer/src/assets/main.css @@ -3,15 +3,56 @@ @tailwind utilities; @layer base { + :root { + --primary: 59, 130, 246; + --secondary: 139, 92, 246; + --accent: 16, 185, 129; + --zinc-50: 250, 250, 251; + --zinc-100: 244, 244, 245; + } + html { scroll-behavior: smooth; } body { - @apply bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 font-sans transition-colors duration-300 antialiased; + @apply bg-zinc-50 text-zinc-900 dark:bg-zinc-950 dark:text-zinc-50 font-sans transition-colors duration-700 antialiased; } h1, h2, h3, h4, h5, h6 { - @apply font-heading; + @apply font-heading tracking-tight; + } + + ::selection { + @apply bg-primary/20 text-primary-dark; + } +} + +@layer components { + .glass-card { + @apply bg-white/70 dark:bg-zinc-900/80 backdrop-blur-xl border border-white/40 dark:border-white/10 shadow-premium; + } + + .glass-card-hover { + @apply hover:shadow-premium-hover hover:border-primary/30 transition-all duration-500; + } + + .input-focus-ring { + @apply border-zinc-200 dark:border-zinc-700/50 transition-all duration-300; + } + + .input-focus-ring:focus-within { + @apply border-primary/50 ring-4 ring-primary/20 shadow-[0_0_20px_rgba(var(--primary),0.15)] dark:shadow-[0_0_25px_rgba(var(--primary),0.25)]; + } + + .custom-scrollbar::-webkit-scrollbar { + width: 6px; + height: 6px; + } + .custom-scrollbar::-webkit-scrollbar-track { + @apply bg-transparent; + } + .custom-scrollbar::-webkit-scrollbar-thumb { + @apply bg-zinc-300/50 dark:bg-zinc-700/50 rounded-full hover:bg-zinc-400/50 dark:hover:bg-zinc-600/50 transition-colors; } } diff --git a/src/renderer/src/components/chat/AttachmentModal.vue b/src/renderer/src/components/chat/AttachmentModal.vue new file mode 100644 index 0000000..bb0b92b --- /dev/null +++ b/src/renderer/src/components/chat/AttachmentModal.vue @@ -0,0 +1,137 @@ + + + diff --git a/src/renderer/src/components/chat/BaseMessageInput.vue b/src/renderer/src/components/chat/BaseMessageInput.vue new file mode 100644 index 0000000..3cc9e6e --- /dev/null +++ b/src/renderer/src/components/chat/BaseMessageInput.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/src/renderer/src/components/chat/ChatHeader.vue b/src/renderer/src/components/chat/ChatHeader.vue index d1c87d6..501466f 100644 --- a/src/renderer/src/components/chat/ChatHeader.vue +++ b/src/renderer/src/components/chat/ChatHeader.vue @@ -10,8 +10,14 @@
-

+

{{ title || 'AI Video Assistant' }} + +
+
+ Processing File +
+

Total Cost: ${{ totalCost.toFixed(4) }} @@ -37,6 +43,12 @@ const totalCost = computed(() => { return videoStore.messages.reduce((acc, msg) => acc + (msg.cost || 0), 0) }) +const activeTasksText = computed(() => { + if (!videoStore.activeBackgroundTasks.length) return '' + const names = videoStore.activeBackgroundTasks.map(t => t.name) + return `Background Tasks:\n${names.join('\n')}` +}) + const handleOpenFolder = async () => { if (videoStore.currentThreadId) { // @ts-ignore diff --git a/src/renderer/src/components/chat/ChatInput.vue b/src/renderer/src/components/chat/ChatInput.vue index cbeaa57..36cbaed 100644 --- a/src/renderer/src/components/chat/ChatInput.vue +++ b/src/renderer/src/components/chat/ChatInput.vue @@ -1,43 +1,33 @@