diff --git a/.claude/agents/backend-algorithm-developer.md b/.claude/agents/backend-algorithm-developer.md new file mode 100644 index 0000000..98fce63 --- /dev/null +++ b/.claude/agents/backend-algorithm-developer.md @@ -0,0 +1,79 @@ +--- +name: backend-algorithm-developer +description: "Use this agent when you need to develop backend services, implement algorithms, or build system components using Java, Python, or Go. Examples include: designing and implementing RESTful APIs, writing efficient algorithms for data processing, creating microservices, optimizing database queries, or building high-performance server applications." +model: sonnet +color: red +memory: user +--- + +You are an expert backend algorithm development engineer with deep proficiency in Java, Python, and Go. You specialize in designing and implementing efficient, scalable backend services and solving complex algorithmic problems. + +**Core Responsibilities:** +- Design and implement robust backend services and APIs +- Write efficient algorithms optimized for performance and scalability +- Choose the appropriate language (Java/Python/Go) based on use case requirements +- Ensure code quality through proper testing and optimization +- Handle database design, caching, and performance tuning + +**Language-Specific Expertise:** +- **Java**: Spring Boot, Spring Cloud, Maven/Gradle, concurrency handling, JVM optimization +- **Python**: FastAPI/Flask/Django, asyncio, data processing libraries, ML integration +- **Go**: Goroutines, channels, Gin/Echo frameworks, microservices patterns + +**Development Approach:** +1. Understand requirements thoroughly before writing code +2. Choose the most appropriate technology stack for the specific use case +3. Write clean, well-documented, and maintainable code +4. Implement proper error handling and logging +5. Consider scalability, performance, and security at every step +6. Write unit tests and integration tests +7. Optimize critical code paths using appropriate data structures and algorithms + +**Quality Standards:** +- Follow language-specific best practices and coding conventions +- Use appropriate design patterns +- Implement proper input validation and security measures +- Ensure code is testable and documented +- Consider edge cases and failure scenarios + +**When to use each language:** +- Use **Java** for enterprise-scale applications, complex transaction systems, and when strong typing and ecosystem libraries are needed +- Use **Python** for rapid prototyping, data processing, ML integration, and scripts +- Use **Go** for high-concurrency services, microservices, and performance-critical components + +Provide well-structured, production-ready code with clear explanations. Always consider the trade-offs of your technical choices. + +# Persistent Agent Memory + +You have a persistent Persistent Agent Memory directory at `C:\Users\caoxiaozhu\.claude\agent-memory\backend-algorithm-developer\`. This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence). Its contents persist across conversations. + +As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned. + +Guidelines: +- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise +- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md +- Update or remove memories that turn out to be wrong or outdated +- Organize memory semantically by topic, not chronologically +- Use the Write and Edit tools to update your memory files + +What to save: +- Stable patterns and conventions confirmed across multiple interactions +- Key architectural decisions, important file paths, and project structure +- User preferences for workflow, tools, and communication style +- Solutions to recurring problems and debugging insights + +What NOT to save: +- Session-specific context (current task details, in-progress work, temporary state) +- Information that might be incomplete — verify against project docs before writing +- Anything that duplicates or contradicts existing CLAUDE.md instructions +- Speculative or unverified conclusions from reading a single file + +Explicit user requests: +- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions +- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files +- When the user corrects you on something you stated from memory, you MUST update or remove the incorrect entry. A correction means the stored memory is wrong — fix it at the source before continuing, so the same mistake does not repeat in future conversations. +- Since this memory is user-scope, keep learnings general since they apply across all projects + +## MEMORY.md + +Your MEMORY.md is currently empty. When you notice a pattern worth preserving across sessions, save it here. Anything in MEMORY.md will be included in your system prompt next time. diff --git a/.claude/agents/elegant-frontend-designer.md b/.claude/agents/elegant-frontend-designer.md new file mode 100644 index 0000000..32f5a55 --- /dev/null +++ b/.claude/agents/elegant-frontend-designer.md @@ -0,0 +1,98 @@ +--- +name: elegant-frontend-designer +description: "Use this agent when you need to create elegant, visually stunning front-end designs for products. Examples include: designing a new landing page, creating a component library, improving existing UI/UX, building a design system, or crafting a complete product interface with modern, sophisticated aesthetics." +model: sonnet +color: purple +memory: project +--- + +You are an elite front-end designer with deep expertise in creating elegant, sophisticated user interfaces. You have mastered the art of combining aesthetics with functionality, understanding that true elegance lies in the balance between visual beauty and seamless user experience. + +**Your Design Philosophy:** +- Embrace minimalism: Less is more. Every element must serve a purpose. +- Typography is paramount: Choose fonts that communicate personality while ensuring readability. +- Color should be intentional: Use restrained palettes with purposeful accent colors. +-Whitespace is your friend: Generous spacing creates breath and sophistication. +- Motion should feel natural: Animations should enhance, not distract. +- Consistency builds trust: A cohesive design system ensures harmony across the product. + +**Technical Expertise:** +You are proficient in: +- Modern CSS (Flexbox, Grid, CSS Variables, Subgrid) +- CSS frameworks (Tailwind CSS, UnoCSS,styled-components) +- Design systems and component libraries +- Responsive and mobile-first design +- Micro-interactions and transitions +- CSS animations and keyframes +- Dark mode and theme switching +- Accessibility standards (WCAG) + +**Design Style References:** +- Apple's human interface guidelines +- Material Design 3 +- Minimalist Japanese design aesthetics +- Swiss design principles +- Modern neumorphism and glassmorphism (when appropriate) +- Subtle gradients and frosted glass effects + +**When designing, you will:** +1. Analyze the requirements and determine the optimal design approach +2. Choose appropriate color palettes, typography, and spacing systems +3. Create responsive, mobile-first layouts +4. Implement elegant micro-interactions and transitions +5. Ensure accessibility and semantic HTML +6. Provide clean, well-structured code +7. Consider performance implications of visual effects + +**Output Format:** +When presenting designs, provide: +- Conceptual overview and design rationale +- Color palette with hex codes +- Typography choices with font families and sizes +- Layout structure (can use ASCII or describe flex/grid) +- Component designs with states +- Animation specifications +- Code implementation (HTML/CSS/JS as appropriate) + +**You will proactively ask clarifying questions when:** +- The target audience or use case is unclear +- Brand guidelines or existing design language conflict with elegant design suggestions +- Technical constraints might limit design choices +- The scope is too broad to provide focused recommendations + +Be confident in your design decisions while remaining open to feedback and iteration. + +# Persistent Agent Memory + +You have a persistent Persistent Agent Memory directory at `D:\Code\Project\YG-Datasets\.claude\agent-memory\elegant-frontend-designer\`. This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence). Its contents persist across conversations. + +As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned. + +Guidelines: +- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise +- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md +- Update or remove memories that turn out to be wrong or outdated +- Organize memory semantically by topic, not chronologically +- Use the Write and Edit tools to update your memory files + +What to save: +- Stable patterns and conventions confirmed across multiple interactions +- Key architectural decisions, important file paths, and project structure +- User preferences for workflow, tools, and communication style +- Solutions to recurring problems and debugging insights + +What NOT to save: +- Session-specific context (current task details, in-progress work, temporary state) +- Information that might be incomplete — verify against project docs before writing +- Anything that duplicates or contradicts existing CLAUDE.md instructions +- Speculative or unverified conclusions from reading a single file + +Explicit user requests: +- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions +- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files +- When the user corrects you on something you stated from memory, you MUST update or remove the incorrect entry. A correction means the stored memory is wrong — fix it at the source before continuing, so the same mistake does not repeat in future conversations. +- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project + +## MEMORY.md + +Your MEMORY.md is currently empty. When you notice a pattern worth preserving across sessions, save it here. Anything in MEMORY.md will be included in your system prompt next time. diff --git a/.claude/agents/robustness-tester-submitter.md b/.claude/agents/robustness-tester-submitter.md new file mode 100644 index 0000000..1941d75 --- /dev/null +++ b/.claude/agents/robustness-tester-submitter.md @@ -0,0 +1,94 @@ +--- +name: robustness-tester-submitter +description: "Use this agent when you need to validate code quality before submission, including testing robustness, error handling, edge cases, and submitting code to repositories. Examples:\\n- After writing a new function, use this agent to test boundary conditions, invalid inputs, and error scenarios to ensure the code handles them gracefully.\\n- Before committing code to the repository, use this agent to run comprehensive robustness tests and submit the validated code.\\n- When refactoring code, use this agent to verify the changes don't introduce new vulnerabilities or failure points." +tools: Glob, Grep, Read, WebFetch, WebSearch +model: opus +color: yellow +memory: project +--- + +You are a senior QA engineer and code robustness expert specializing in testing software reliability and handling code submission workflows. + +**Core Responsibilities:** +1. **Robustness Testing**: Evaluate code for resilience against: + - Edge cases and boundary conditions + - Invalid or unexpected inputs + - Race conditions and concurrency issues + - Resource exhaustion (memory, CPU, file handles) + - Network failures and timeouts + - Error handling completeness + +2. **Code Submission**: Handle the process of committing and pushing code to repositories, including: + - Running pre-submission checks + - Creating meaningful commit messages + - Following repository conventions + - Handling merge conflicts if needed + +**Testing Methodologies:** +- **Boundary Value Analysis**: Test at and beyond input limits +- **Equivalence Partitioning**: Group inputs into valid/invalid partitions +- **Fault Injection**: Introduce failures to test recovery mechanisms +- **Stress Testing**: Push code beyond normal operational limits +- **Negative Testing**: Verify proper handling of invalid scenarios + +**Quality Standards:** +- All critical paths must have proper error handling +- Input validation must occur at entry points +- Resource cleanup must be guaranteed (use defer, finally, etc.) +- Concurrent code must have proper synchronization +- External dependencies should have appropriate timeouts and fallbacks + +**Submission Process:** +1. Run all existing tests to ensure no regressions +2. Execute robustness test suite +3. Verify code passes linting and formatting standards +4. Stage changes with appropriate git commands +5. Create descriptive commit messages following conventional commits format +6. Push to remote repository + +**Output Expectations:** +- Provide detailed test results with pass/fail status +- Document any robustness issues found with severity levels +- Suggest specific fixes for identified problems +- Confirm successful submission with commit hash + +**Update your agent memory** as you discover common robustness patterns, testing strategies, and code submission workflows. Record: +- Common failure modes in different code patterns +- Effective test cases that catch edge case bugs +- Repository-specific submission conventions +- Successful robustness testing approaches + +# Persistent Agent Memory + +You have a persistent Persistent Agent Memory directory at `D:\Code\Project\YG-Datasets\.claude\agent-memory\robustness-tester-submitter\`. This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence). Its contents persist across conversations. + +As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned. + +Guidelines: +- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise +- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md +- Update or remove memories that turn out to be wrong or outdated +- Organize memory semantically by topic, not chronologically +- Use the Write and Edit tools to update your memory files + +What to save: +- Stable patterns and conventions confirmed across multiple interactions +- Key architectural decisions, important file paths, and project structure +- User preferences for workflow, tools, and communication style +- Solutions to recurring problems and debugging insights + +What NOT to save: +- Session-specific context (current task details, in-progress work, temporary state) +- Information that might be incomplete — verify against project docs before writing +- Anything that duplicates or contradicts existing CLAUDE.md instructions +- Speculative or unverified conclusions from reading a single file + +Explicit user requests: +- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions +- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files +- When the user corrects you on something you stated from memory, you MUST update or remove the incorrect entry. A correction means the stored memory is wrong — fix it at the source before continuing, so the same mistake does not repeat in future conversations. +- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project + +## MEMORY.md + +Your MEMORY.md is currently empty. When you notice a pattern worth preserving across sessions, save it here. Anything in MEMORY.md will be included in your system prompt next time. diff --git a/.claude/agents/ux-ui-requirements-analyst.md b/.claude/agents/ux-ui-requirements-analyst.md new file mode 100644 index 0000000..ed12da0 --- /dev/null +++ b/.claude/agents/ux-ui-requirements-analyst.md @@ -0,0 +1,95 @@ +--- +name: ux-ui-requirements-analyst +description: "Use this agent when you need to analyze user requirements, evaluate UX/UI design quality, assess interface reasonableness, provide recommendations for improving user experience, or review design consistency and usability in a project." +tools: Glob, Grep, Read, WebFetch, WebSearch +model: sonnet +color: blue +memory: project +--- + +You are an expert Requirements Analyst specializing in UX/UI evaluation and interface design analysis. Your role is to help projects thoroughly analyze user requirements, evaluate the quality and reasonableness of UX/UI designs, and provide actionable recommendations for improvement. + +**Your expertise includes:** +- User experience (UX) analysis and best practices +- User interface (UI) design principles and standards +- Interface usability and reasonableness evaluation +- User requirements gathering and analysis +- Design consistency and coherence assessment +- Accessibility considerations (WCAG guidelines) +- User flow and journey mapping +- Information architecture evaluation + +**Your approach to analysis:** +1. Examine the design or requirements from multiple perspectives: + - Visual hierarchy and layout structure + - Color scheme, typography, and visual consistency + - Interactive elements and feedback mechanisms + - Navigation and information architecture + - Consistency across different screens/pages + - Accessibility and inclusivity + - Overall user satisfaction and task efficiency + +2. For each analysis, identify: + - Strengths and good practices + - Issues, pain points, or potential improvements + - Specific, actionable recommendations + - Priority of improvements based on user impact + +3. Provide rationale for your recommendations, referencing established UX/UI principles and best practices when possible. + +**When analyzing interface reasonableness:** +- Evaluate if the interface aligns with user expectations and mental models +- Check if workflows are intuitive and efficient +- Assess if error prevention and recovery mechanisms are adequate +- Verify that key features are easily discoverable +- Consider the learning curve for new users + +**Important guidelines:** +- Ask clarifying questions when project context, target users, or business objectives are unclear +- Consider both user needs and technical feasibility in recommendations +- Provide concrete examples or references to design patterns when helpful +- Be constructive and solution-oriented in your feedback +- When analyzing existing designs, be specific about what works and what doesn't + +**Output format:** +Structure your analysis clearly with: +- Summary of findings +- Strengths identified +- Issues/areas for improvement (prioritized) +- Specific recommendations with rationale +- Optional: Questions for further clarification + +# Persistent Agent Memory + +You have a persistent Persistent Agent Memory directory at `D:\Code\Project\YG-Datasets\.claude\agent-memory\ux-ui-requirements-analyst\`. This directory already exists — write to it directly with the Write tool (do not run mkdir or check for its existence). Its contents persist across conversations. + +As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned. + +Guidelines: +- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise +- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md +- Update or remove memories that turn out to be wrong or outdated +- Organize memory semantically by topic, not chronologically +- Use the Write and Edit tools to update your memory files + +What to save: +- Stable patterns and conventions confirmed across multiple interactions +- Key architectural decisions, important file paths, and project structure +- User preferences for workflow, tools, and communication style +- Solutions to recurring problems and debugging insights + +What NOT to save: +- Session-specific context (current task details, in-progress work, temporary state) +- Information that might be incomplete — verify against project docs before writing +- Anything that duplicates or contradicts existing CLAUDE.md instructions +- Speculative or unverified conclusions from reading a single file + +Explicit user requests: +- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it — no need to wait for multiple interactions +- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files +- When the user corrects you on something you stated from memory, you MUST update or remove the incorrect entry. A correction means the stored memory is wrong — fix it at the source before continuing, so the same mistake does not repeat in future conversations. +- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project + +## MEMORY.md + +Your MEMORY.md is currently empty. When you notice a pattern worth preserving across sessions, save it here. Anything in MEMORY.md will be included in your system prompt next time. diff --git a/.gitignore b/.gitignore index 36b13f1..34775a9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Package lock files (optional - uncomment if you want to ignore them) +# package-lock.json +# yarn.lock +# pnpm-lock.yaml + # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index 2daa85e..23cf4f9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ -# YG-Datasets +# YG-Dataset 本地启动指南 +## 快速启动 + +### 1. 安装后端依赖 + +```bash +cd backend +pip install -r requirements.txt +``` + +### 2. 启动后端 + +```bash +cd backend +uvicorn app.main:app --reload --port 8000 +``` + +后端地址: http://localhost:8000 +API 文档: http://localhost:8000/docs + +### 3. 安装前端依赖 + +```bash +cd frontend +npm install +``` + +### 4. 启动前端 + +```bash +npm run dev +``` + +前端地址: http://localhost:3000 + +--- + +## 目录结构 + +``` +YG-Datasets/ +├── backend/ # FastAPI 后端 +│ ├── app/ +│ │ ├── api/v1/ # API 路由 +│ │ ├── models/ # 数据库模型 +│ │ └── services/ # 业务逻辑 +│ └── requirements.txt +├── frontend/ # Vue 3 前端 +│ ├── src/ +│ │ ├── views/ # 页面 +│ │ └── api/ # API 封装 +│ └── package.json +└── uploads/ # 上传文件存储目录 +``` + +## 默认配置 + +- 数据库: SQLite (`backend/ygdataset.db`) +- 上传目录: `backend/uploads/` +- 后端端口: 8000 +- 前端端口: 3000 diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..15f7d65 --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,27 @@ +FROM python:3.11-slim + +WORKDIR /app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + build-essential \ + libpq-dev \ + && rm -rf /var/lib/apt/lists/* + +# Copy requirements +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application +COPY . . + +# Create uploads directory +RUN mkdir -p uploads + +# Expose port +EXPOSE 8000 + +# Run application +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] diff --git a/backend/app/api/__init__.py b/backend/app/api/__init__.py new file mode 100644 index 0000000..aeea91b --- /dev/null +++ b/backend/app/api/__init__.py @@ -0,0 +1,3 @@ +""" +API module initialization +""" diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py new file mode 100644 index 0000000..aacac86 --- /dev/null +++ b/backend/app/api/v1/__init__.py @@ -0,0 +1,17 @@ +""" +API v1 Router +""" + +from fastapi import APIRouter + +from app.api.v1 import files, projects, chunks, questions, datasets, eval + +api_router = APIRouter() + +# Include sub-routers +api_router.include_router(projects.router, prefix="/projects", tags=["projects"]) +api_router.include_router(files.router, prefix="/files", tags=["files"]) +api_router.include_router(chunks.router, prefix="/chunks", tags=["chunks"]) +api_router.include_router(questions.router, prefix="/questions", tags=["questions"]) +api_router.include_router(datasets.router, prefix="/datasets", tags=["datasets"]) +api_router.include_router(eval.router, prefix="/eval", tags=["eval"]) diff --git a/backend/app/api/v1/chunks/__init__.py b/backend/app/api/v1/chunks/__init__.py new file mode 100644 index 0000000..d5d4b6b --- /dev/null +++ b/backend/app/api/v1/chunks/__init__.py @@ -0,0 +1,182 @@ +""" +Chunks API Router +""" +from typing import List, Optional +from uuid import UUID +from pydantic import BaseModel +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.core.database import get_db +from app.models.models import Chunk, File +from app.schemas.base import ChunkCreate, ChunkResponse +from app.services.text_splitter.splitter import get_splitter +from app.services.file_processor.pdf_processor import process_pdf +from app.services.file_processor.docx_processor import process_docx +from app.services.file_processor.excel_processor import process_csv, process_excel + +router = APIRouter() + + +class SplitRequest(BaseModel): + """Request model for splitting text""" + file_id: Optional[UUID] = None + method: str = "recursive" + chunk_size: int = 500 + overlap: int = 50 + separator: Optional[str] = None + + +class ChunkListResponse(BaseModel): + """Response for chunk list""" + chunks: List[ChunkResponse] + total: int + + +def process_file_by_type(file: File) -> str: + """Process file based on its type""" + if not file.file_path: + raise HTTPException(status_code=400, detail="File path not found") + + processors = { + "pdf": process_pdf, + "docx": process_docx, + "xlsx": process_excel, + "csv": process_csv, + } + + processor = processors.get(file.file_type) + if not processor: + # Return raw text for txt, md files + with open(file.file_path, 'r', encoding='utf-8') as f: + return f.read() + + return processor(file.file_path) + + +@router.post("/split", response_model=dict) +async def split_text( + project_id: UUID, + request: SplitRequest, + db: AsyncSession = Depends(get_db) +): + """Split text into chunks""" + # Get file + if request.file_id: + result = await db.execute( + select(File).where(File.id == request.file_id, File.project_id == project_id) + ) + file = result.scalar_one_or_none() + if not file: + raise HTTPException(status_code=404, detail="File not found") + + # Process file + text = process_file_by_type(file) + + # Update file status + file.status = "processing" + await db.commit() + else: + raise HTTPException(status_code=400, detail="file_id is required") + + # Split text + kwargs = {"chunk_size": request.chunk_size, "overlap": request.overlap} + if request.method == "custom" and request.separator: + kwargs["separator"] = request.separator + + splitter = get_splitter(request.method, **kwargs) + split_results = splitter.split(text) + + # Save chunks + chunks = [] + for chunk_data in split_results: + db_chunk = Chunk( + project_id=project_id, + file_id=file.id, + name=chunk_data.get("name", f"Chunk {chunk_data['index'] + 1}"), + content=chunk_data["content"], + word_count=chunk_data.get("word_count", len(chunk_data["content"].split())) + ) + db.add(db_chunk) + chunks.append(db_chunk) + + await db.commit() + + # Update file status + file.status = "completed" + await db.commit() + + return {"chunks": len(chunks), "message": f"Successfully split into {len(chunks)} chunks"} + + +@router.get("/", response_model=dict) +async def list_chunks( + project_id: UUID, + file_id: Optional[UUID] = Query(None), + db: AsyncSession = Depends(get_db) +): + """List chunks for a project""" + query = select(Chunk).where(Chunk.project_id == project_id) + + if file_id: + query = query.where(Chunk.file_id == file_id) + + query = query.order_by(Chunk.created_at.desc()) + + result = await db.execute(query) + chunks = result.scalars().all() + + return { + "chunks": [ChunkResponse.model_validate(c) for c in chunks], + "total": len(chunks) + } + + +@router.get("/{chunk_id}", response_model=dict) +async def get_chunk(project_id: UUID, chunk_id: UUID, db: AsyncSession = Depends(get_db)): + """Get chunk by ID""" + result = await db.execute( + select(Chunk).where(Chunk.id == chunk_id, Chunk.project_id == project_id) + ) + chunk = result.scalar_one_or_none() + if not chunk: + raise HTTPException(status_code=404, detail="Chunk not found") + return ChunkResponse.model_validate(chunk) + + +@router.put("/{chunk_id}", response_model=dict) +async def update_chunk( + project_id: UUID, + chunk_id: UUID, + chunk: ChunkCreate, + db: AsyncSession = Depends(get_db) +): + """Update chunk""" + result = await db.execute( + select(Chunk).where(Chunk.id == chunk_id, Chunk.project_id == project_id) + ) + db_chunk = result.scalar_one_or_none() + if not db_chunk: + raise HTTPException(status_code=404, detail="Chunk not found") + + for key, value in chunk.model_dump(exclude_unset=True).items(): + setattr(db_chunk, key, value) + + await db.commit() + await db.refresh(db_chunk) + return ChunkResponse.model_validate(db_chunk) + + +@router.delete("/{chunk_id}", response_model=dict) +async def delete_chunk(project_id: UUID, chunk_id: UUID, db: AsyncSession = Depends(get_db)): + """Delete chunk""" + result = await db.execute( + select(Chunk).where(Chunk.id == chunk_id, Chunk.project_id == project_id) + ) + chunk = result.scalar_one_or_none() + if not chunk: + raise HTTPException(status_code=404, detail="Chunk not found") + + await db.delete(chunk) + await db.commit() + return {"message": "Chunk deleted successfully"} diff --git a/backend/app/api/v1/datasets/__init__.py b/backend/app/api/v1/datasets/__init__.py new file mode 100644 index 0000000..ba5c30d --- /dev/null +++ b/backend/app/api/v1/datasets/__init__.py @@ -0,0 +1,126 @@ +""" +Datasets API Router +""" +from typing import List, Optional +from uuid import UUID +from pydantic import BaseModel +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, func +from app.core.database import get_db +from app.models.models import Dataset, Question +from app.schemas.base import DatasetCreate, DatasetResponse + +router = APIRouter() + + +class ExportRequest(BaseModel): + """Export request schema""" + format: str = "alpaca" # alpaca, sharegpt, llama_factory, json + + +@router.get("/", response_model=dict) +async def list_datasets(project_id: UUID, db: AsyncSession = Depends(get_db)): + """List datasets for a project""" + result = await db.execute( + select(Dataset).where(Dataset.project_id == project_id).order_by(Dataset.created_at.desc()) + ) + datasets = result.scalars().all() + + # Get question count for each dataset + dataset_list = [] + for dataset in datasets: + dataset_data = DatasetResponse.model_validate(dataset) + # TODO: Count questions in dataset + dataset_data.question_count = 0 + dataset_list.append(dataset_data) + + return {"datasets": dataset_list} + + +@router.post("/", response_model=dict) +async def create_dataset( + project_id: UUID, + dataset: DatasetCreate, + db: AsyncSession = Depends(get_db) +): + """Create a new dataset""" + db_dataset = Dataset(project_id=project_id, **dataset.model_dump()) + db.add(db_dataset) + await db.commit() + await db.refresh(db_dataset) + + return {"id": str(db_dataset.id)} + + +@router.get("/{dataset_id}", response_model=dict) +async def get_dataset( + project_id: UUID, + dataset_id: UUID, + db: AsyncSession = Depends(get_db) +): + """Get dataset by ID""" + result = await db.execute( + select(Dataset).where(Dataset.id == dataset_id, Dataset.project_id == project_id) + ) + dataset = result.scalar_one_or_none() + if not dataset: + raise HTTPException(status_code=404, detail="Dataset not found") + + return DatasetResponse.model_validate(dataset) + + +@router.delete("/{dataset_id}", response_model=dict) +async def delete_dataset( + project_id: UUID, + dataset_id: UUID, + db: AsyncSession = Depends(get_db) +): + """Delete dataset""" + result = await db.execute( + select(Dataset).where(Dataset.id == dataset_id, Dataset.project_id == project_id) + ) + dataset = result.scalar_one_or_none() + if not dataset: + raise HTTPException(status_code=404, detail="Dataset not found") + + await db.delete(dataset) + await db.commit() + + return {"message": "Dataset deleted successfully"} + + +@router.post("/{dataset_id}/export") +async def export_dataset( + project_id: UUID, + dataset_id: UUID, + request: ExportRequest, + db: AsyncSession = Depends(get_db) +): + """Export dataset in specified format""" + # TODO: Implement actual export logic + + # Get dataset + result = await db.execute( + select(Dataset).where(Dataset.id == dataset_id, Dataset.project_id == project_id) + ) + dataset = result.scalar_one_or_none() + if not dataset: + raise HTTPException(status_code=404, detail="Dataset not found") + + # Get questions for this dataset (placeholder) + # In real implementation, would link questions to datasets + + # Return sample data based on format + sample_data = [ + { + "instruction": "这是一个示例指令", + "input": "", + "output": "这是一个示例输出" + } + ] + + if request.format == "json": + return sample_data + + return {"data": sample_data, "format": request.format} diff --git a/backend/app/api/v1/eval/__init__.py b/backend/app/api/v1/eval/__init__.py new file mode 100644 index 0000000..6feb6fd --- /dev/null +++ b/backend/app/api/v1/eval/__init__.py @@ -0,0 +1,100 @@ +""" +Evaluation API Router +""" +from typing import List, Optional +from uuid import UUID +from pydantic import BaseModel +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.core.database import get_db +from app.models.models import EvalDataset, Task +from app.schemas.base import EvalDatasetCreate, EvalDatasetResponse, TaskResponse + +router = APIRouter() + + +class GenerateEvalRequest(BaseModel): + """Request for generating evaluation dataset""" + name: str + question_type: str = "mixed" + count: int = 50 + + +class RunEvalRequest(BaseModel): + """Request for running evaluation""" + model_config_id: Optional[UUID] = None + + +@router.get("/", response_model=dict) +async def list_eval_datasets(project_id: UUID, db: AsyncSession = Depends(get_db)): + """List evaluation datasets""" + result = await db.execute( + select(EvalDataset).where(EvalDataset.project_id == project_id).order_by(EvalDataset.created_at.desc()) + ) + datasets = result.scalars().all() + + return {"datasets": [EvalDatasetResponse.model_validate(d) for d in datasets]} + + +@router.post("/", response_model=dict) +async def create_eval_dataset( + project_id: UUID, + request: GenerateEvalRequest, + db: AsyncSession = Depends(get_db) +): + """Create evaluation dataset""" + db_dataset = EvalDataset( + project_id=project_id, + name=request.name, + question_type=request.question_type + ) + db.add(db_dataset) + await db.commit() + await db.refresh(db_dataset) + + return {"id": str(db_dataset.id)} + + +@router.post("/{eval_id}/evaluate", response_model=dict) +async def run_evaluation( + project_id: UUID, + eval_id: UUID, + request: RunEvalRequest, + db: AsyncSession = Depends(get_db) +): + """Run evaluation on dataset""" + # Check dataset exists + result = await db.execute( + select(EvalDataset).where(EvalDataset.id == eval_id, EvalDataset.project_id == project_id) + ) + dataset = result.scalar_one_or_none() + if not dataset: + raise HTTPException(status_code=404, detail="Evaluation dataset not found") + + # Create evaluation task + task = Task( + project_id=project_id, + task_type="eval", + status="pending" + ) + db.add(task) + await db.commit() + await db.refresh(task) + + # TODO: Start evaluation in background + + return {"task_id": str(task.id), "message": "Evaluation task started"} + + +@router.get("/results", response_model=dict) +async def get_eval_results(project_id: UUID, task_id: UUID, db: AsyncSession = Depends(get_db)): + """Get evaluation results""" + result = await db.execute( + select(Task).where(Task.id == task_id, Task.project_id == project_id) + ) + task = result.scalar_one_or_none() + if not task: + raise HTTPException(status_code=404, detail="Task not found") + + return TaskResponse.model_validate(task) diff --git a/backend/app/api/v1/files/__init__.py b/backend/app/api/v1/files/__init__.py new file mode 100644 index 0000000..b9a281e --- /dev/null +++ b/backend/app/api/v1/files/__init__.py @@ -0,0 +1,110 @@ +""" +Files API Router +""" +import os +import aiofiles +from pathlib import Path +from typing import List +from uuid import UUID +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.core.database import get_db +from app.core.config import get_settings +from app.models.models import File +from app.schemas.base import FileResponse + +settings = get_settings() +router = APIRouter() + +# Ensure upload directory exists +UPLOAD_DIR = Path(settings.UPLOAD_DIR) +UPLOAD_DIR.mkdir(parents=True, exist_ok=True) + + +def get_file_type(filename: str) -> str: + """Get file type from extension""" + ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else '' + type_map = { + 'pdf': 'pdf', + 'docx': 'docx', + 'doc': 'docx', + 'xlsx': 'xlsx', + 'xls': 'xlsx', + 'csv': 'csv', + 'epub': 'epub', + 'md': 'md', + 'markdown': 'md', + 'txt': 'txt' + } + return type_map.get(ext, 'txt') + + +@router.post("/upload", response_model=dict) +async def upload_file( + project_id: UUID, + file: UploadFile = File(...), + db: AsyncSession = Depends(get_db) +): + """Upload a file""" + # Save file to disk + file_path = UPLOAD_DIR / f"{project_id}_{file.filename}" + async with aiofiles.open(file_path, 'wb') as f: + content = await file.read() + await f.write(content) + + # Create file record + db_file = File( + project_id=project_id, + filename=file.filename, + file_type=get_file_type(file.filename), + file_path=str(file_path), + size=len(content), + status="pending" + ) + db.add(db_file) + await db.commit() + await db.refresh(db_file) + + return {"id": str(db_file.id), "filename": db_file.filename, "status": db_file.status} + + +@router.get("/", response_model=dict) +async def list_files(project_id: UUID, db: AsyncSession = Depends(get_db)): + """List files for a project""" + result = await db.execute( + select(File).where(File.project_id == project_id).order_by(File.created_at.desc()) + ) + files = result.scalars().all() + return {"files": [FileResponse.model_validate(f) for f in files]} + + +@router.get("/{file_id}", response_model=dict) +async def get_file(project_id: UUID, file_id: UUID, db: AsyncSession = Depends(get_db)): + """Get file by ID""" + result = await db.execute( + select(File).where(File.id == file_id, File.project_id == project_id) + ) + file = result.scalar_one_or_none() + if not file: + raise HTTPException(status_code=404, detail="File not found") + return FileResponse.model_validate(file) + + +@router.delete("/{file_id}", response_model=dict) +async def delete_file(project_id: UUID, file_id: UUID, db: AsyncSession = Depends(get_db)): + """Delete file""" + result = await db.execute( + select(File).where(File.id == file_id, File.project_id == project_id) + ) + file = result.scalar_one_or_none() + if not file: + raise HTTPException(status_code=404, detail="File not found") + + # Delete file from disk + if file.file_path and os.path.exists(file.file_path): + os.remove(file.file_path) + + await db.delete(file) + await db.commit() + return {"message": "File deleted successfully"} diff --git a/backend/app/api/v1/projects/__init__.py b/backend/app/api/v1/projects/__init__.py new file mode 100644 index 0000000..535d190 --- /dev/null +++ b/backend/app/api/v1/projects/__init__.py @@ -0,0 +1,74 @@ +""" +Projects API Router +""" +from typing import List +from uuid import UUID +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.core.database import get_db +from app.models.models import Project +from app.schemas.base import ( + ProjectCreate, + ProjectUpdate, + ProjectResponse +) + +router = APIRouter() + + +@router.get("/", response_model=dict) +async def list_projects(db: AsyncSession = Depends(get_db)): + """List all projects""" + result = await db.execute(select(Project).order_by(Project.created_at.desc())) + projects = result.scalars().all() + return {"projects": [ProjectResponse.model_validate(p) for p in projects]} + + +@router.post("/", response_model=dict) +async def create_project(project: ProjectCreate, db: AsyncSession = Depends(get_db)): + """Create a new project""" + db_project = Project(**project.model_dump()) + db.add(db_project) + await db.commit() + await db.refresh(db_project) + return {"id": str(db_project.id)} + + +@router.get("/{project_id}", response_model=dict) +async def get_project(project_id: UUID, db: AsyncSession = Depends(get_db)): + """Get project by ID""" + result = await db.execute(select(Project).where(Project.id == project_id)) + project = result.scalar_one_or_none() + if not project: + raise HTTPException(status_code=404, detail="Project not found") + return ProjectResponse.model_validate(project) + + +@router.put("/{project_id}", response_model=dict) +async def update_project(project_id: UUID, project: ProjectUpdate, db: AsyncSession = Depends(get_db)): + """Update project""" + result = await db.execute(select(Project).where(Project.id == project_id)) + db_project = result.scalar_one_or_none() + if not db_project: + raise HTTPException(status_code=404, detail="Project not found") + + for key, value in project.model_dump(exclude_unset=True).items(): + setattr(db_project, key, value) + + await db.commit() + await db.refresh(db_project) + return ProjectResponse.model_validate(db_project) + + +@router.delete("/{project_id}", response_model=dict) +async def delete_project(project_id: UUID, db: AsyncSession = Depends(get_db)): + """Delete project""" + result = await db.execute(select(Project).where(Project.id == project_id)) + project = result.scalar_one_or_none() + if not project: + raise HTTPException(status_code=404, detail="Project not found") + + await db.delete(project) + await db.commit() + return {"message": "Project deleted successfully"} diff --git a/backend/app/api/v1/questions/__init__.py b/backend/app/api/v1/questions/__init__.py new file mode 100644 index 0000000..fa4e7b7 --- /dev/null +++ b/backend/app/api/v1/questions/__init__.py @@ -0,0 +1,122 @@ +""" +Questions API Router +""" +from typing import List, Optional +from uuid import UUID +from pydantic import BaseModel +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select +from app.core.database import get_db +from app.models.models import Question, Chunk +from app.schemas.base import QuestionCreate, QuestionResponse + +router = APIRouter() + + +class GenerateRequest(BaseModel): + """Request model for generating questions""" + chunk_ids: List[UUID] = [] + count: int = 5 + question_types: List[str] = ["fact", "summary"] + + +@router.post("/generate", response_model=dict) +async def generate_questions( + project_id: UUID, + request: GenerateRequest, + db: AsyncSession = Depends(get_db) +): + """Generate questions from chunks using LLM""" + # TODO: Implement LLM-based question generation + # This is a placeholder that creates sample questions + + if not request.chunk_ids: + raise HTTPException(status_code=400, detail="chunk_ids is required") + + # Get chunks + result = await db.execute( + select(Chunk).where(Chunk.id.in_(request.chunk_ids), Chunk.project_id == project_id) + ) + chunks = result.scalars().all() + + if not chunks: + raise HTTPException(status_code=404, detail="No chunks found") + + # Create sample questions (placeholder) + created_questions = [] + for chunk in chunks: + for i in range(request.count): + question = Question( + project_id=project_id, + chunk_id=chunk.id, + content=f"这是关于「{chunk.name}」的问题 {i+1}?", + answer=f"这是问题 {i+1} 的答案。", + question_type=request.question_types[0] if request.question_types else "fact", + source="generated" + ) + db.add(question) + created_questions.append(question) + + await db.commit() + + return { + "questions": len(created_questions), + "message": f"Successfully generated {len(created_questions)} questions" + } + + +@router.get("/", response_model=dict) +async def list_questions( + project_id: UUID, + chunk_id: Optional[UUID] = Query(None), + db: AsyncSession = Depends(get_db) +): + """List questions for a project""" + query = select(Question).where(Question.project_id == project_id) + + if chunk_id: + query = query.where(Question.chunk_id == chunk_id) + + result = await db.execute(query) + questions = result.scalars().all() + + return {"questions": [QuestionResponse.model_validate(q) for q in questions]} + + +@router.put("/{question_id}", response_model=dict) +async def update_question( + project_id: UUID, + question_id: UUID, + question: QuestionCreate, + db: AsyncSession = Depends(get_db) +): + """Update question""" + result = await db.execute( + select(Question).where(Question.id == question_id, Question.project_id == project_id) + ) + db_question = result.scalar_one_or_none() + if not db_question: + raise HTTPException(status_code=404, detail="Question not found") + + for key, value in question.model_dump(exclude_unset=True).items(): + setattr(db_question, key, value) + + await db.commit() + await db.refresh(db_question) + return QuestionResponse.model_validate(db_question) + + +@router.delete("/{question_id}", response_model=dict) +async def delete_question(project_id: UUID, question_id: UUID, db: AsyncSession = Depends(get_db)): + """Delete question""" + result = await db.execute( + select(Question).where(Question.id == question_id, Question.project_id == project_id) + ) + question = result.scalar_one_or_none() + if not question: + raise HTTPException(status_code=404, detail="Question not found") + + await db.delete(question) + await db.commit() + return {"message": "Question deleted successfully"} diff --git a/backend/app/core/__init__.py b/backend/app/core/__init__.py new file mode 100644 index 0000000..11a535b --- /dev/null +++ b/backend/app/core/__init__.py @@ -0,0 +1,3 @@ +""" +Core module initialization +""" diff --git a/backend/app/core/config.py b/backend/app/core/config.py new file mode 100644 index 0000000..6acca40 --- /dev/null +++ b/backend/app/core/config.py @@ -0,0 +1,49 @@ +""" +Application Configuration +""" + +from functools import lru_cache +from pydantic_settings import BaseSettings +from pydantic import Field + + +class Settings(BaseSettings): + """Application settings""" + + # App + APP_NAME: str = "YG-Dataset" + DEBUG: bool = True + HOST: str = "0.0.0.0" + PORT: int = 8000 + + # Database - 使用 SQLite 进行开发/测试 + # 生产环境可切换为 PostgreSQL + DATABASE_URL: str = Field( + default="sqlite:///./ygdataset.db", + description="Database connection URL (sqlite:// or postgresql+asyncpg://)" + ) + DATABASE_URL_SYNC: str = Field( + default="sqlite:///./ygdataset.db", + description="Synchronous database connection URL" + ) + + # Redis + REDIS_URL: str = "redis://localhost:6379/0" + + # File Storage + UPLOAD_DIR: str = "./uploads" + MAX_FILE_SIZE: int = 100 * 1024 * 1024 # 100MB + + # LLM Settings + DEFAULT_MODEL_PROVIDER: str = "openai" + DEFAULT_MODEL_NAME: str = "gpt-4o-mini" + + class Config: + env_file = ".env" + extra = "allow" + + +@lru_cache() +def get_settings() -> Settings: + """Get cached settings""" + return Settings() diff --git a/backend/app/core/database.py b/backend/app/core/database.py new file mode 100644 index 0000000..56bcd6a --- /dev/null +++ b/backend/app/core/database.py @@ -0,0 +1,68 @@ +""" +Database Configuration and Session Management +支持 SQLite 和 PostgreSQL +""" + +from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker +from sqlalchemy.orm import DeclarativeBase +from sqlalchemy import create_engine +from app.core.config import get_settings + +settings = get_settings() + + +def get_engine_config(): + """根据数据库类型返回引擎配置""" + if settings.DATABASE_URL.startswith("sqlite"): + return {"echo": settings.DEBUG} + else: + return { + "echo": settings.DEBUG, + "pool_pre_ping": True, + "pool_size": 10, + "max_overflow": 20, + } + + +# Async engine for FastAPI +async_engine = create_async_engine( + settings.DATABASE_URL, + **get_engine_config() +) + +# Sync engine for migrations +sync_engine = create_engine( + settings.DATABASE_URL_SYNC, + echo=settings.DEBUG, + pool_pre_ping=True, +) + + +# Async session factory +AsyncSessionLocal = async_sessionmaker( + async_engine, + class_=AsyncSession, + expire_on_commit=False, + autocommit=False, + autoflush=False, +) + + +class Base(DeclarativeBase): + """Base class for all models""" + pass + + +async def init_db(): + """Initialize database tables""" + async with async_engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + +async def get_db() -> AsyncSession: + """Dependency for getting database session""" + async with AsyncSessionLocal() as session: + try: + yield session + finally: + await session.close() diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..04249dd --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,58 @@ +""" +YG-Dataset Backend Application +FastAPI-based API server for dataset generation platform +""" + +from contextlib import asynccontextmanager +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware + +from app.api.v1 import api_router +from app.core.config import settings +from app.core.database import init_db + + +@asynccontextmanager +async def lifespan(app: FastAPI): + """Application lifespan events""" + # Startup + await init_db() + yield + # Shutdown + pass + + +app = FastAPI( + title="YG-Dataset API", + description="Dataset Generation Platform API", + version="1.0.0", + lifespan=lifespan, +) + +# CORS +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include API routes +app.include_router(api_router, prefix="/api/v1") + + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return {"status": "healthy", "version": "1.0.0"} + + +if __name__ == "__main__": + import uvicorn + uvicorn.run( + "app.main:app", + host=settings.HOST, + port=settings.PORT, + reload=settings.DEBUG, + ) diff --git a/backend/app/models/__init__.py b/backend/app/models/__init__.py new file mode 100644 index 0000000..ae10ffe --- /dev/null +++ b/backend/app/models/__init__.py @@ -0,0 +1,3 @@ +""" +Database Models +""" diff --git a/backend/app/models/base.py b/backend/app/models/base.py new file mode 100644 index 0000000..4a57e6c --- /dev/null +++ b/backend/app/models/base.py @@ -0,0 +1,19 @@ +""" +Base Model with UUID support +""" +import uuid +from datetime import datetime +from sqlalchemy import Column, DateTime +from sqlalchemy.dialects.postgresql import UUID +from app.core.database import Base + + +class TimestampMixin: + """Mixin for created_at and updated_at timestamps""" + created_at = Column(DateTime, default=datetime.utcnow, nullable=False) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False) + + +class UUIDMixin: + """Mixin for UUID primary key""" + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True) diff --git a/backend/app/models/models.py b/backend/app/models/models.py new file mode 100644 index 0000000..0a8696e --- /dev/null +++ b/backend/app/models/models.py @@ -0,0 +1,161 @@ +""" +Database Models for YG-Dataset +""" +from sqlalchemy import Column, String, Text, Integer, BigInteger, ForeignKey, JSON +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.orm import relationship +from app.core.database import Base +from app.models.base import UUIDMixin, TimestampMixin + + +class Project(Base, UUIDMixin, TimestampMixin): + """Project model""" + __tablename__ = "projects" + + name = Column(String(255), nullable=False) + description = Column(Text) + + # Relationships + files = relationship("File", back_populates="project", cascade="all, delete-orphan") + chunks = relationship("Chunk", back_populates="project", cascade="all, delete-orphan") + tags = relationship("Tag", back_populates="project", cascade="all, delete-orphan") + datasets = relationship("Dataset", back_populates="project", cascade="all, delete-orphan") + eval_datasets = relationship("EvalDataset", back_populates="project", cascade="all, delete-orphan") + model_configs = relationship("ModelConfig", back_populates="project", cascade="all, delete-orphan") + tasks = relationship("Task", back_populates="project", cascade="all, delete-orphan") + + +class File(Base, UUIDMixin, TimestampMixin): + """File model for uploaded documents""" + __tablename__ = "files" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) + filename = Column(String(255), nullable=False) + file_type = Column(String(50), nullable=False) # pdf, docx, xlsx, csv, epub, md, txt + file_path = Column(String(500)) + size = Column(BigInteger) # file size in bytes + status = Column(String(20), default="pending") # pending, processing, completed, failed + + # Relationships + project = relationship("Project", back_populates="files") + chunks = relationship("Chunk", back_populates="file", cascade="all, delete-orphan") + + +class Chunk(Base, UUIDMixin, TimestampMixin): + """Text chunk model after splitting""" + __tablename__ = "chunks" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) + file_id = Column(UUID(as_uuid=True), ForeignKey("files.id", ondelete="CASCADE")) + name = Column(String(255)) + content = Column(Text, nullable=False) + summary = Column(Text) + word_count = Column(Integer) + metadata = Column(JSON) # store additional info like headings, page numbers + + # Relationships + project = relationship("Project", back_populates="chunks") + file = relationship("File", back_populates="chunks") + questions = relationship("Question", back_populates="chunk", cascade="all, delete-orphan") + chunk_tags = relationship("ChunkTag", back_populates="chunk", cascade="all, delete-orphan") + + +class Tag(Base, UUIDMixin, TimestampMixin): + """Tag/Label model for categorizing content""" + __tablename__ = "tags" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) + label = Column(String(255), nullable=False) + parent_id = Column(UUID(as_uuid=True), ForeignKey("tags.id", ondelete="CASCADE")) + color = Column(String(20)) # hex color code + + # Relationships + project = relationship("Project", back_populates="tags") + parent = relationship("Tag", remote_side="Tag.id", back_populates="children") + children = relationship("Tag", back_populates="parent") + chunk_tags = relationship("ChunkTag", back_populates="tag") + + +class ChunkTag(Base, UUIDMixin): + """Many-to-many relationship between chunks and tags""" + __tablename__ = "chunk_tags" + + chunk_id = Column(UUID(as_uuid=True), ForeignKey("chunks.id", ondelete="CASCADE"), nullable=False) + tag_id = Column(UUID(as_uuid=True), ForeignKey("tags.id", ondelete="CASCADE"), nullable=False) + + # Relationships + chunk = relationship("Chunk", back_populates="chunk_tags") + tag = relationship("Tag", back_populates="chunk_tags") + + +class Question(Base, UUIDMixin, TimestampMixin): + """Question/QA pair model""" + __tablename__ = "questions" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) + chunk_id = Column(UUID(as_uuid=True), ForeignKey("chunks.id", ondelete="CASCADE")) + content = Column(Text, nullable=False) # question content + answer = Column(Text) # answer content + question_type = Column(String(50)) # fact, summary, reasoning, etc. + source = Column(String(50), default="manual") # manual, generated + + # Relationships + project = relationship("Project") + chunk = relationship("Chunk", back_populates="questions") + + +class Dataset(Base, UUIDMixin, TimestampMixin): + """Dataset model""" + __tablename__ = "datasets" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) + name = Column(String(255), nullable=False) + description = Column(Text) + dataset_type = Column(String(50)) # qa, conversation, instruction + metadata = Column(JSON) + + # Relationships + project = relationship("Project", back_populates="datasets") + + +class EvalDataset(Base, UUIDMixin, TimestampMixin): + """Evaluation dataset model""" + __tablename__ = "eval_datasets" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) + name = Column(String(255), nullable=False) + question_type = Column(String(50)) # mixed, fact, reasoning + metadata = Column(JSON) + + # Relationships + project = relationship("Project", back_populates="eval_datasets") + + +class ModelConfig(Base, UUIDMixin, TimestampMixin): + """Model configuration for LLM providers""" + __tablename__ = "model_configs" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE"), nullable=False) + provider = Column(String(50), nullable=False) # openai, anthropic, ollama, custom + model_name = Column(String(100)) + api_key = Column(String(500)) + api_base = Column(String(500)) + is_default = Column(String(10), default="false") + + # Relationships + project = relationship("Project", back_populates="model_configs") + + +class Task(Base, UUIDMixin, TimestampMixin): + """Task model for background jobs""" + __tablename__ = "tasks" + + project_id = Column(UUID(as_uuid=True), ForeignKey("projects.id", ondelete="CASCADE")) + task_type = Column(String(50)) # split, generate, eval, export + status = Column(String(20), default="pending") # pending, running, completed, failed + progress = Column(Integer, default=0) # 0-100 + result = Column(JSON) + error = Column(Text) + + # Relationships + project = relationship("Project", back_populates="tasks") diff --git a/backend/app/schemas/__init__.py b/backend/app/schemas/__init__.py new file mode 100644 index 0000000..c89615a --- /dev/null +++ b/backend/app/schemas/__init__.py @@ -0,0 +1,3 @@ +""" +Pydantic Schemas +""" diff --git a/backend/app/schemas/base.py b/backend/app/schemas/base.py new file mode 100644 index 0000000..82fc865 --- /dev/null +++ b/backend/app/schemas/base.py @@ -0,0 +1,170 @@ +""" +Base Pydantic schemas +""" +from datetime import datetime +from typing import Optional, Any +from uuid import UUID +from pydantic import BaseModel, ConfigDict + + +class TimestampMixin(BaseModel): + """Mixin for timestamps""" + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + + +class UUIDMixin(BaseModel): + """Mixin for UUID""" + model_config = ConfigDict(from_attributes=True) + + id: UUID + + +class ProjectBase(BaseModel): + """Base project schema""" + name: str + description: Optional[str] = None + + +class ProjectCreate(ProjectBase): + """Project create schema""" + pass + + +class ProjectUpdate(ProjectBase): + """Project update schema""" + pass + + +class ProjectResponse(ProjectBase, UUIDMixin, TimestampMixin): + """Project response schema""" + pass + + +class FileBase(BaseModel): + """Base file schema""" + filename: str + file_type: str + size: Optional[int] = None + + +class FileResponse(FileBase, UUIDMixin, TimestampMixin): + """File response schema""" + status: str + + +class ChunkBase(BaseModel): + """Base chunk schema""" + name: Optional[str] = None + content: str + summary: Optional[str] = None + word_count: Optional[int] = None + + +class ChunkCreate(ChunkBase): + """Chunk create schema""" + file_id: Optional[UUID] = None + + +class ChunkResponse(ChunkBase, UUIDMixin, TimestampMixin): + """Chunk response schema""" + pass + + +class QuestionBase(BaseModel): + """Base question schema""" + content: str + answer: Optional[str] = None + question_type: Optional[str] = None + + +class QuestionCreate(QuestionBase): + """Question create schema""" + chunk_id: Optional[UUID] = None + + +class QuestionResponse(QuestionBase, UUIDMixin, TimestampMixin): + """Question response schema""" + source: str + + +class DatasetBase(BaseModel): + """Base dataset schema""" + name: str + description: Optional[str] = None + dataset_type: Optional[str] = None + + +class DatasetCreate(DatasetBase): + """Dataset create schema""" + pass + + +class DatasetResponse(DatasetBase, UUIDMixin, TimestampMixin): + """Dataset response schema""" + question_count: Optional[int] = None + + +class EvalDatasetBase(BaseModel): + """Base eval dataset schema""" + name: str + question_type: Optional[str] = None + + +class EvalDatasetCreate(EvalDatasetBase): + """Eval dataset create schema""" + pass + + +class EvalDatasetResponse(EvalDatasetBase, UUIDMixin, TimestampMixin): + """Eval dataset response schema""" + pass + + +class TagBase(BaseModel): + """Base tag schema""" + label: str + parent_id: Optional[UUID] = None + color: Optional[str] = None + + +class TagCreate(TagBase): + """Tag create schema""" + pass + + +class TagResponse(TagBase, UUIDMixin, TimestampMixin): + """Tag response schema""" + pass + + +class ModelConfigBase(BaseModel): + """Base model config schema""" + provider: str + model_name: Optional[str] = None + api_key: Optional[str] = None + api_base: Optional[str] = None + is_default: Optional[str] = "false" + + +class ModelConfigCreate(ModelConfigBase): + """Model config create schema""" + pass + + +class ModelConfigResponse(ModelConfigBase, UUIDMixin, TimestampMixin): + """Model config response schema""" + pass + + +class TaskBase(BaseModel): + """Base task schema""" + task_type: str + status: Optional[str] = "pending" + progress: Optional[int] = 0 + + +class TaskResponse(TaskBase, UUIDMixin, TimestampMixin): + """Task response schema""" + result: Optional[Any] = None + error: Optional[str] = None diff --git a/backend/app/services/__init__.py b/backend/app/services/__init__.py new file mode 100644 index 0000000..04f16e9 --- /dev/null +++ b/backend/app/services/__init__.py @@ -0,0 +1,3 @@ +""" +Services module +""" diff --git a/backend/app/services/file_processor/__init__.py b/backend/app/services/file_processor/__init__.py new file mode 100644 index 0000000..5bb0762 --- /dev/null +++ b/backend/app/services/file_processor/__init__.py @@ -0,0 +1,3 @@ +""" +File Processing Services +""" diff --git a/backend/app/services/file_processor/docx_processor.py b/backend/app/services/file_processor/docx_processor.py new file mode 100644 index 0000000..a638830 --- /dev/null +++ b/backend/app/services/file_processor/docx_processor.py @@ -0,0 +1,53 @@ +""" +DOCX Text Extractor +""" +from docx import Document +from typing import Dict, List + + +class DOCXProcessor: + """Extract text from DOCX files""" + + def extract_text(self, file_path: str) -> str: + """Extract all text from DOCX""" + doc = Document(file_path) + text_parts = [] + + for para in doc.paragraphs: + if para.text.strip(): + text_parts.append(para.text) + + # Also extract text from tables + for table in doc.tables: + for row in table.rows: + for cell in row.cells: + if cell.text.strip(): + text_parts.append(cell.text) + + return "\n\n".join(text_parts) + + def extract_with_metadata(self, file_path: str) -> Dict: + """Extract text with DOCX metadata""" + doc = Document(file_path) + + result = { + "text": self.extract_text(file_path), + "paragraphs": len(doc.paragraphs), + "tables": len(doc.tables), + "sections": len(doc.sections), + "metadata": { + "author": doc.core_properties.author, + "title": doc.core_properties.title, + "subject": doc.core_properties.subject, + "created": doc.core_properties.created, + "modified": doc.core_properties.modified + } + } + + return result + + +def process_docx(file_path: str) -> str: + """Process DOCX file and return text""" + processor = DOCXProcessor() + return processor.extract_text(file_path) diff --git a/backend/app/services/file_processor/excel_processor.py b/backend/app/services/file_processor/excel_processor.py new file mode 100644 index 0000000..5c4e2b7 --- /dev/null +++ b/backend/app/services/file_processor/excel_processor.py @@ -0,0 +1,66 @@ +""" +Excel/CSV Text Extractor +""" +import pandas as pd +from typing import Dict, List + + +class ExcelProcessor: + """Extract text from Excel and CSV files""" + + def extract_csv(self, file_path: str) -> str: + """Extract text from CSV file""" + df = pd.read_csv(file_path) + return self._dataframe_to_text(df) + + def extract_excel(self, file_path: str, sheet_name: str = None) -> str: + """Extract text from Excel file""" + if sheet_name: + df = pd.read_excel(file_path, sheet_name=sheet_name) + return self._dataframe_to_text(df) + else: + # Read all sheets + sheets = pd.read_excel(file_path, sheet_name=None) + text_parts = [] + for sheet_name, df in sheets.items(): + text_parts.append(f"=== Sheet: {sheet_name} ===\n") + text_parts.append(self._dataframe_to_text(df)) + return "\n\n".join(text_parts) + + def _dataframe_to_text(self, df: pd.DataFrame) -> str: + """Convert DataFrame to readable text""" + text_parts = [] + + # Add column headers + if not df.empty: + text_parts.append(" | ".join(str(col) for col in df.columns)) + text_parts.append("-" * len(text_parts[-1])) + + # Add rows + for _, row in df.iterrows(): + row_text = " | ".join(str(val) for val in row.values) + text_parts.append(row_text) + + return "\n".join(text_parts) + + def extract_all_sheets(self, file_path: str) -> Dict[str, str]: + """Extract all sheets from Excel file""" + sheets = pd.read_excel(file_path, sheet_name=None) + return {name: self._dataframe_to_text(df) for name, df in sheets.items()} + + def get_sheet_names(self, file_path: str) -> List[str]: + """Get all sheet names from Excel file""" + xl = pd.ExcelFile(file_path) + return xl.sheet_names + + +def process_csv(file_path: str) -> str: + """Process CSV file and return text""" + processor = ExcelProcessor() + return processor.extract_csv(file_path) + + +def process_excel(file_path: str) -> str: + """Process Excel file and return text""" + processor = ExcelProcessor() + return processor.extract_excel(file_path) diff --git a/backend/app/services/file_processor/pdf_processor.py b/backend/app/services/file_processor/pdf_processor.py new file mode 100644 index 0000000..9826fb2 --- /dev/null +++ b/backend/app/services/file_processor/pdf_processor.py @@ -0,0 +1,65 @@ +""" +PDF Text Extractor +""" +import pdfplumber +from typing import Dict, List, Optional + + +class PDFProcessor: + """Extract text from PDF files""" + + def extract_text(self, file_path: str) -> str: + """Extract all text from PDF""" + text_parts = [] + + with pdfplumber.open(file_path) as pdf: + for page_num, page in enumerate(pdf.pages, 1): + text = page.extract_text() + if text: + text_parts.append(f"--- Page {page_num} ---\n{text}") + + return "\n\n".join(text_parts) + + def extract_pages(self, file_path: str) -> List[Dict]: + """Extract text page by page with metadata""" + pages = [] + + with pdfplumber.open(file_path) as pdf: + for page_num, page in enumerate(pdf.pages, 1): + text = page.extract_text() + if text: + pages.append({ + "page_number": page_num, + "text": text.strip(), + "word_count": len(text.split()) + }) + + return pages + + def extract_with_metadata(self, file_path: str) -> Dict: + """Extract text with PDF metadata""" + result = { + "text": "", + "pages": [], + "metadata": {} + } + + with pdfplumber.open(file_path) as pdf: + # Get metadata + result["metadata"] = { + "page_count": len(pdf.pages), + "metadata": pdf.metadata + } + + # Extract pages + pages = self.extract_pages(file_path) + result["pages"] = pages + result["text"] = "\n\n".join([p["text"] for p in pages]) + + return result + + +def process_pdf(file_path: str) -> str: + """Process PDF file and return text""" + processor = PDFProcessor() + return processor.extract_with_metadata(file_path)["text"] diff --git a/backend/app/services/text_splitter/__init__.py b/backend/app/services/text_splitter/__init__.py new file mode 100644 index 0000000..8037217 --- /dev/null +++ b/backend/app/services/text_splitter/__init__.py @@ -0,0 +1,3 @@ +""" +Text Splitter Services +""" diff --git a/backend/app/services/text_splitter/splitter.py b/backend/app/services/text_splitter/splitter.py new file mode 100644 index 0000000..a024faf --- /dev/null +++ b/backend/app/services/text_splitter/splitter.py @@ -0,0 +1,248 @@ +""" +Text Splitter +""" +import re +from typing import List, Dict, Optional + + +class TextSplitter: + """Base text splitter""" + + def __init__(self, chunk_size: int = 500, overlap: int = 50): + self.chunk_size = chunk_size + self.overlap = overlap + + def split(self, text: str) -> List[Dict]: + """Split text into chunks""" + raise NotImplementedError + + +class RecursiveTextSplitter(TextSplitter): + """Recursive character text splitter""" + + def __init__(self, chunk_size: int = 500, overlap: int = 50, separators: List[str] = None): + super().__init__(chunk_size, overlap) + self.separators = separators or ["\n\n", "\n", ". ", " ", ""] + + def split(self, text: str) -> List[Dict]: + """Split text recursively""" + chunks = [] + current_chunk = "" + chunk_index = 0 + + for separator in self.separators: + if separator in text: + parts = text.split(separator) + for part in parts: + if len(current_chunk) + len(part) > self.chunk_size: + if current_chunk: + chunks.append({ + "index": chunk_index, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + chunk_index += 1 + + # Handle overlap + if self.overlap > 0 and chunks: + overlap_text = " ".join(chunks[-1]["content"].split()[-self.overlap:]) + current_chunk = overlap_text + separator + part + else: + current_chunk = part + else: + current_chunk += separator + part if current_chunk else part + + if current_chunk: + chunks.append({ + "index": chunk_index, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + break + else: + continue + + return chunks + + +class MarkdownStructureSplitter(TextSplitter): + """Split text based on Markdown structure (headings)""" + + def __init__(self, chunk_size: int = 2000, overlap: int = 100): + super().__init__(chunk_size, overlap) + + def split(self, text: str) -> List[Dict]: + """Split text by Markdown headings""" + # Find all heading patterns + heading_pattern = r'^(#{1,6})\s+(.+)$' + lines = text.split('\n') + + chunks = [] + current_chunk = "" + current_heading = "文档开头" + chunk_index = 0 + + for line in lines: + heading_match = re.match(heading_pattern, line.strip()) + + if heading_match: + # Save previous chunk if exists + if current_chunk.strip(): + chunks.append({ + "index": chunk_index, + "name": current_heading, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + chunk_index += 1 + + current_heading = heading_match.group(2).strip() + current_chunk = line + "\n" + else: + # Check chunk size + if len(current_chunk) > self.chunk_size: + chunks.append({ + "index": chunk_index, + "name": current_heading, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + chunk_index += 1 + + # Handle overlap + if self.overlap > 0: + overlap_lines = current_chunk.split('\n')[-self.overlap:] + current_chunk = '\n'.join(overlap_lines) + '\n' + else: + current_chunk = "" + + current_chunk += line + "\n" + + # Add last chunk + if current_chunk.strip(): + chunks.append({ + "index": chunk_index, + "name": current_heading, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + + return chunks + + +class TokenSplitter(TextSplitter): + """Split text by token count""" + + def __init__(self, chunk_size: int = 500, overlap: int = 50): + super().__init__(chunk_size, overlap) + + def split(self, text: str) -> List[Dict]: + """Split text by approximate token count""" + words = text.split() + chunks = [] + chunk_index = 0 + + for i in range(0, len(words), self.chunk_size - self.overlap): + chunk_words = words[i:i + self.chunk_size] + chunk_text = " ".join(chunk_words) + + chunks.append({ + "index": chunk_index, + "content": chunk_text, + "word_count": len(chunk_words), + "token_estimate": len(chunk_words) * 1.3 # rough token estimate + }) + chunk_index += 1 + + return chunks + + +class CodeSplitter(TextSplitter): + """Split text with code awareness""" + + def __init__(self, chunk_size: int = 500, overlap: int = 50): + super().__init__(chunk_size, overlap) + + def split(self, text: str) -> List[Dict]: + """Split text preserving code blocks""" + # Split by code blocks first + code_pattern = r'```[\s\S]*?```' + parts = re.split(code_pattern, text) + + chunks = [] + chunk_index = 0 + current_chunk = "" + + for part in parts: + if len(current_chunk) + len(part) > self.chunk_size: + if current_chunk.strip(): + chunks.append({ + "index": chunk_index, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + chunk_index += 1 + current_chunk = part + else: + current_chunk += part + + if current_chunk.strip(): + chunks.append({ + "index": chunk_index, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + + return chunks + + +class CustomSplitter(TextSplitter): + """Custom separator splitter""" + + def __init__(self, separator: str = "\n\n", chunk_size: int = 500): + super().__init__(chunk_size, 0) + self.separator = separator + + def split(self, text: str) -> List[Dict]: + """Split by custom separator""" + parts = text.split(self.separator) + chunks = [] + + current_chunk = "" + chunk_index = 0 + + for part in parts: + if len(current_chunk) + len(part) > self.chunk_size: + if current_chunk.strip(): + chunks.append({ + "index": chunk_index, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + chunk_index += 1 + current_chunk = part + else: + current_chunk += self.separator + part if current_chunk else part + + if current_chunk.strip(): + chunks.append({ + "index": chunk_index, + "content": current_chunk.strip(), + "word_count": len(current_chunk.split()) + }) + + return chunks + + +def get_splitter(method: str, **kwargs) -> TextSplitter: + """Get text splitter by method name""" + splitters = { + "recursive": RecursiveTextSplitter, + "markdown_structure": MarkdownStructureSplitter, + "token": TokenSplitter, + "code": CodeSplitter, + "custom": CustomSplitter + } + + splitter_class = splitters.get(method, RecursiveTextSplitter) + return splitter_class(**kwargs) diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..57e9731 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,37 @@ +# FastAPI +fastapi>=0.115.0 +uvicorn[standard]>=0.30.0 +python-multipart>=0.0.9 + +# Database - SQLite (默认), PostgreSQL 可选 +sqlalchemy>=2.0.0 +alembic>=1.13.0 +# asyncpg>=0.29.0 # PostgreSQL 异步驱动(生产环境使用) +# psycopg2-binary>=2.9.9 # PostgreSQL 同步驱动 + +# Pydantic +pydantic>=2.0.0 +pydantic-settings>=2.0.0 + +# Redis - 可选,用于缓存/队列(开发环境可省略) +# redis>=5.0.0 + +# File Processing +pdfplumber>=0.10.4 +python-docx>=1.1.0 +openpyxl>=3.1.2 +pandas>=2.2.0 +ebooklib>=0.5 +PyMuPDF>=1.24.0 + +# LLM & Text +langchain>=0.3.0 +langchain-community>=0.2.0 +langchain-openai>=0.1.0 +tiktoken>=0.7.0 +python-dotenv>=1.0.0 + +# Utils +python-dateutil>=2.8.2 +httpx>=0.27.0 +aiofiles>=23.2.1 diff --git a/bug修改.md b/bug修改.md new file mode 100644 index 0000000..7d0394c --- /dev/null +++ b/bug修改.md @@ -0,0 +1,20 @@ +# Bug 修改记录 + +## 2026-03-17 + +### 初始项目创建 +- 创建 YG-Dataset 重构项目 +- 搭建 FastAPI + Vue 3 基础架构 + +--- + +## 修复记录格式 + +### 日期 +**问题描述:** +**原因:** +**修复方案:** + +--- + +*持续更新中...* diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e670599 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +version: '3.8' + +services: + # FastAPI 后端 (SQLite 数据库,随项目文件存储) + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: ygdataset-backend + ports: + - "8000:8000" + environment: + - DATABASE_URL=sqlite:///./ygdataset.db + - DEBUG=true + volumes: + - ./backend:/app + - uploads:/app/uploads + restart: unless-stopped + + # Vue 前端 + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + container_name: ygdataset-frontend + ports: + - "3000:80" + volumes: + - ./frontend:/app + - /app/node_modules + depends_on: + - backend + restart: unless-stopped + +volumes: + uploads: + +# 如需 PostgreSQL,取消注释以下配置: +# services: +# postgres: +# image: postgres:15 +# environment: +# POSTGRES_USER: ygdataset +# POSTGRES_PASSWORD: your_password +# POSTGRES_DB: ygdataset +# ports: +# - "5432:5432" +# volumes: +# - postgres_data:/var/lib/postgresql/data + +# volumes: +# postgres_data: diff --git a/easy-dataset-main-架构分析报告.md b/easy-dataset-main-架构分析报告.md new file mode 100644 index 0000000..3cc9687 --- /dev/null +++ b/easy-dataset-main-架构分析报告.md @@ -0,0 +1,306 @@ +# Easy Dataset 项目架构分析报告 + +## 一、项目概述 + +**Easy Dataset** 是一个功能强大的大模型微调数据集创建工具,由 ConardLi 开发维护。该应用提供直观的界面和强大的内置文档解析、智能分割、数据清洗和增强功能,可将各种格式的领域文档转换为高质量的结构化数据集,适用于模型微调、RAG(检索增强生成)和模型性能评估等场景。 + +**项目地址**: https://github.com/ConardLi/easy-dataset +**当前版本**: 1.7.2 +**许可证**: AGPL 3.0 + +--- + +## 二、技术栈分析 + +### 2.1 核心框架 + +| 类别 | 技术选型 | 说明 | +|------|----------|------| +| 前端框架 | Next.js 14 | App Router 架构 | +| UI 框架 | Material-UI (MUI) | v5.16.14 | +| 状态管理 | Jotai | 轻量级原子化状态管理 | +| 数据库 | Prisma + SQLite | 使用 Prisma ORM | +| 开发语言 | JavaScript | 全栈 JavaScript | + +### 2.2 关键依赖 + +| 类别 | 库名称 | 用途 | +|------|--------|------| +| AI/ML | ai SDK, langchain | 大模型集成 | +| LLM 提供商 | @ai-sdk/openai, ollama-ai-provider, zhipu-ai-provider | 多模型支持 | +| 国际化 | i18next, react-i18next | 多语言支持 | +| 文档处理 | @opendocsg/pdf2md, mammoth, pdf2md-js | PDF/DOCX 解析 | +| 桌面应用 | Electron | 跨平台桌面客户端 | +| 数据处理 | xlsx, adm-zip, jszip | 文件处理 | + +### 2.3 开发工具 + +- **包管理器**: pnpm +- **代码规范**: ESLint + Prettier +- **Git Hooks**: Husky + lint-staged +- **构建工具**: electron-builder (桌面应用打包) + +--- + +## 三、目录结构 + +``` +easy-dataset-main/ +├── app/ # Next.js 应用目录 (App Router) +│ ├── api/ # API 路由 (150+ 个路由) +│ │ ├── check-update/ # 版本检查 +│ │ ├── llm/ # LLM 模型相关 API +│ │ │ ├── fetch-models/ # 获取模型列表 +│ │ │ ├── model/ # 模型配置 +│ │ │ ├── ollama/ # Ollama 本地模型 +│ │ │ └── providers/ # LLM 提供商 +│ │ ├── monitoring/ # 监控 API +│ │ │ ├── logs/ # 日志 +│ │ │ ├── stats/ # 统计 +│ │ │ └── summary/ # 摘要 +│ │ └── projects/ # 项目相关 API +│ │ └── [projectId]/ # 动态项目路由 +│ │ ├── chunks/ # 文本分块 +│ │ ├── datasets/ # 数据集 +│ │ ├── eval-datasets/ # 评估数据集 +│ │ ├── eval-tasks/ # 评估任务 +│ │ ├── files/ # 文件管理 +│ │ ├── images/ # 图片处理 +│ │ ├── questions/ # 问题生成 +│ │ ├── distill/ # 数据蒸馏 +│ │ ├── blind-test-tasks/ # 盲测任务 +│ │ ├── playground/ # 模型测试场 +│ │ └── ... +│ └── (页面路由) +├── components/ # React 组件 (100+ 组件) +│ ├── common/ # 通用组件 +│ ├── home/ # 首页组件 +│ ├── Navbar/ # 导航栏 +│ ├── dataset-square/ # 数据集广场 +│ ├── datasets/ # 数据集组件 +│ ├── distill/ # 数据蒸馏组件 +│ ├── export/ # 导出组件 +│ ├── questions/ # 问题组件 +│ ├── text-split/ # 文本分割组件 +│ ├── tasks/ # 任务管理组件 +│ ├── playground/ # 测试场组件 +│ └── settings/ # 设置组件 +├── prisma/ # 数据库 schema +│ ├── schema.prisma # Prisma 数据模型 +│ ├── sql.json # SQL 模板 +│ └── generate-template.js # 模板生成 +├── locales/ # 国际化资源 +│ ├── en/ # 英文 +│ ├── zh-CN/ # 简体中文 +│ └── pt-BR/ # 葡萄牙语 +├── electron/ # Electron 桌面应用 +│ ├── main.js # 主进程 +│ └── preload.js # 预加载脚本 +├── public/ # 静态资源 +├── desktop/ # 桌面端入口 +└── package.json # 项目配置 +``` + +--- + +## 四、核心模块设计 + +### 4.1 数据模型 (Prisma Schema) + +项目使用 Prisma ORM 管理数据,主要数据模型包括: + +- **Project**: 项目 +- **File**: 上传的文件 +- **Chunk**: 文本分块 +- **Question**: 生成的问题 +- **Dataset**: 微调数据集 +- **EvalDataset**: 评估数据集 +- **EvalTask**: 评估任务 +- **BlindTestTask**: 盲测任务 +- **ModelConfig**: 模型配置 +- **Tag**: 标签 +- **Conversation**: 对话记录 +- **Image**: 图片数据 +- **Task**: 后台任务 + +### 4.2 核心功能模块 + +#### 4.2.1 文档处理模块 (Text Split) + +- 支持 PDF、Markdown、DOCX、TXT、EPUB 格式 +- 多种分割算法:Markdown结构、递归分隔符、固定长度、代码感知分块 +- 目录结构提取 +- PDF 转 Markdown + +#### 4.2.2 问题生成模块 (Question Generation) + +- 自动从文本片段提取相关问题 +- 问题模板管理 +- 批量生成 +- 标签树自动构建 + +#### 4.2.3 数据集生成模块 (Dataset Generation) + +- 单轮问答数据集 +- 多轮对话数据集 +- 图片问答数据集 +- 数据蒸馏(无需上传文档) + +#### 4.2.4 评估模块 (Evaluation) + +- 评估数据集生成(判断题、单选、多选、简答、开放题) +- 自动化模型评估(Judge Model) +- 人类盲测系统(Arena) +- AI 质量评估 + +#### 4.2.5 LLM 集成模块 + +支持的模型提供商: +- OpenAI +- Ollama (本地模型) +- 智谱 AI +- 阿里百炼 +- OpenRouter +- Google Gemini +- Anthropic Claude + +--- + +## 五、API 架构 + +### 5.1 API 设计原则 + +- RESTful 风格路由 +- 基于 Next.js App Router 的 Route Handlers +- 使用 Zod 进行请求/响应验证 + +### 5.2 主要 API 分组 + +| API 分组 | 路由前缀 | 功能 | +|----------|----------|------| +| 项目管理 | `/api/projects` | 项目 CRUD | +| 文件管理 | `/api/projects/[id]/files` | 文件上传/处理 | +| 文本分块 | `/api/projects/[id]/chunks` | 文本分割 | +| 问题生成 | `/api/projects/[id]/questions` | 问题生成/管理 | +| 数据集 | `/api/projects/[id]/datasets` | 数据集管理 | +| 评估 | `/api/projects/[id]/eval-*` | 评估相关 | +| 盲测 | `/api/projects/[id]/blind-test-tasks` | 盲测系统 | +| LLM | `/api/llm/*` | 模型配置/调用 | +| 监控 | `/api/monitoring/*` | 日志/统计 | + +--- + +## 六、前端架构 + +### 6.1 组件设计模式 + +- **Jotai 状态管理**: 使用原子化状态管理,便于细粒度更新 +- **MUI 组件库**: 统一的 UI 组件 +- **Framer Motion**: 动画效果 + +### 6.2 主要页面 + +1. **首页** (`/`): 项目列表、创建项目、统计卡片 +2. **项目页** (`/projects/[id]`): + - 文本分割 (`/text-split`) + - 问题列表 (`/questions`) + - 数据集 (`/datasets`) + - 评估 (`/eval-datasets`) + - 盲测 Arena (`/arena`) + - 设置 (`/settings`) +3. **模型测试场** (`/playground`) +4. **数据集广场** (`/datasets-square`) + +--- + +## 七、部署架构 + +### 7.1 多平台支持 + +- **Web 应用**: Next.js 生产构建 +- **桌面应用**: Electron + - Windows (NSIS 安装包) + - macOS (DMG) + - Linux (AppImage) +- **Docker**: 支持 Docker 部署 + +### 7.2 开发命令 + +```bash +# 开发 +pnpm dev # 启动开发服务器 (端口 1717) + +# 构建 +pnpm build # 构建 Next.js 生产版本 +pnpm electron-build # 构建桌面应用 + +# 数据库 +pnpm db:push # 推送 schema 到数据库 +pnpm db:studio # 打开 Prisma Studio +``` + +--- + +## 八、数据流设计 + +### 8.1 核心业务流程 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 上传文档 │ -> │ 文本分割 │ -> │ 问题生成 │ -> │ 数据集生成 │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ │ │ │ + PDF/DOCX Chunk Question Dataset + Markdown 目录结构 标签树 导出格式 +``` + +### 8.2 评估流程 + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ 评估数据集 │ -> │ 评估任务 │ -> │ 模型评估 │ -> │ 结果分析 │ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + 生成题目 批量处理 Judge Model Arena盲测 +``` + +--- + +## 九、国际化 + +- **技术选型**: i18next + react-i18next +- **支持语言**: + - 英文 (en) + - 简体中文 (zh-CN) + - 土耳其语 (tr) + - 葡萄牙语 (pt-BR) +- **语言检测**: i18next-browser-languagedetector + +--- + +## 十、特性亮点 + +1. **智能文档处理**: 支持多种格式,智能识别 +2. **多种分割算法**: 灵活适应不同文档结构 +3. **自动标签树**: 基于文档结构智能构建 +4. **多类型数据集**: 单轮问答、多轮对话、图片问答 +5. **完整评估体系**: 自动化评估 + 人类盲测 +6. **多模型支持**: 兼容 OpenAI 格式的所有 API +7. **一键导出**: 支持多种格式和 LLaMA Factory 集成 +8. **桌面客户端**: 跨平台支持 + +--- + +## 十一、扩展方向 + +根据项目发展路线,未来可能扩展的方向包括: + +1. 更多文件格式支持 +2. 数据集版本管理 +3. 团队协作功能 +4. 更多导出格式 +5. 更强大的数据分析功能 + +--- + +*报告生成时间: 2026-03-17* +*基于 easy-dataset-main 项目源码分析* diff --git a/easy-dataset-main/.dockerignore b/easy-dataset-main/.dockerignore new file mode 100644 index 0000000..a57a79f --- /dev/null +++ b/easy-dataset-main/.dockerignore @@ -0,0 +1,16 @@ +node_modules +.next +.git +.github +README.md +README.zh-CN.md +.gitignore +.env.local +.env.development.local +.env.test.local +.env.production.local +/test +/local-db +/video +/prisma/*.sqlite +/prisma/*.sqlite-* \ No newline at end of file diff --git a/easy-dataset-main/.gitattributes b/easy-dataset-main/.gitattributes new file mode 100644 index 0000000..192a66d --- /dev/null +++ b/easy-dataset-main/.gitattributes @@ -0,0 +1,6 @@ +# Ensure shell scripts always use LF line endings +*.sh text eol=lf +docker-entrypoint.sh text eol=lf + +# Ensure Dockerfile uses LF +Dockerfile text eol=lf diff --git a/easy-dataset-main/.github/ISSUE_TEMPLATE/bug_report.md b/easy-dataset-main/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..616b030 --- /dev/null +++ b/easy-dataset-main/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[Bug]' +labels: bug +assignees: '' +--- + +**注意:请务必按照此模版填写 ISSUES 信息,否则 ISSUE 将不会得到回复** + +**问题描述** +清晰、简洁地描述该问题的具体情况。 + +**桌面设备(请完善以下信息)** + +- 操作系统:[例如:、Window、MAC] +- 浏览器:[例如:谷歌浏览器(Chrome),苹果浏览器(Safari)] +- Easy Dataset 版本:[例如:1.2.2] + +**使用模型** + +- 模型提供商:例如火山引擎 +- 模型名称:例如 DeepSeek R1 + +**复现步骤** +重现该问题的操作步骤: + +1. 进入“……”页面。 +2. 点击“……”。 +3. 向下滚动到“……”。 +4. 这时会看到错误提示。 + +**预期结果** +清晰、简洁地描述你原本期望出现的情况。 + +**截图** +如果有必要,请附上截图,以便更好地说明你的问题。 + +**其他相关信息** +在此处添加关于该问题的其他任何相关背景信息。 diff --git a/easy-dataset-main/.github/ISSUE_TEMPLATE/feature-or-enhancement-.md b/easy-dataset-main/.github/ISSUE_TEMPLATE/feature-or-enhancement-.md new file mode 100644 index 0000000..da71e69 --- /dev/null +++ b/easy-dataset-main/.github/ISSUE_TEMPLATE/feature-or-enhancement-.md @@ -0,0 +1,19 @@ +--- +name: 'Feature or enhancement ' +about: Suggest an idea for this project +title: '[Feature]' +labels: enhancement +assignees: '' +--- + +**你的功能请求是否与某个问题相关?请描述。** +清晰、简洁地描述一下存在的问题是什么。例如:当我[具体情况]时,我总是感到很沮丧。 + +**描述你期望的解决方案** +清晰、简洁地描述你希望实现的情况。 + +**描述你考虑过的替代方案** +清晰、简洁地描述你所考虑过的任何其他解决方案或功能。 + +**其他相关信息** +在此处添加与该功能请求相关的其他任何背景信息或截图。 diff --git a/easy-dataset-main/.github/ISSUE_TEMPLATE/question.md b/easy-dataset-main/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..fe396af --- /dev/null +++ b/easy-dataset-main/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,40 @@ +--- +name: Question +about: Ask questions you want to know +title: '[Question]' +labels: question +assignees: '' +--- + +**注意:请务必按照此模版填写 ISSUES 信息,否则 ISSUE 将不会得到回复** + +**问题描述** +清晰、简洁地描述该问题的具体情况。 + +**桌面设备(请完善以下信息)** + +- 操作系统:[例如:、Window、MAC] +- 浏览器:[例如:谷歌浏览器(Chrome),苹果浏览器(Safari)] +- Easy Dataset 版本:[例如:1.2.2] + +**使用模型** + +- 模型提供商:例如火山引擎 +- 模型名称:例如 DeepSeek R1 + +**复现步骤** +重现该问题的操作步骤: + +1. 进入“……”页面。 +2. 点击“……”。 +3. 向下滚动到“……”。 +4. 这时会看到错误提示。 + +**预期结果** +清晰、简洁地描述你原本期望出现的情况。 + +**截图** +如果有必要,请附上截图,以便更好地说明你的问题。 + +**其他相关信息** +在此处添加关于该问题的其他任何相关背景信息。 diff --git a/easy-dataset-main/.github/PULL_REQUEST_TEMPLATE.md b/easy-dataset-main/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..bbef272 --- /dev/null +++ b/easy-dataset-main/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,12 @@ +### 变更类型- [ ] 新功能(feat) + +- [ ] 修复(fix) +- [ ] 文档(docs) +- [ ] 重构(refactor) + +### 变更描述- 简要说明修改内容(关联Issue:#123) + +### 文档更新- [ ] README.md + +- [ ] 贡献指南 +- [ ] 接口文档(如有) diff --git a/easy-dataset-main/.github/workflows/docker-build.yml b/easy-dataset-main/.github/workflows/docker-build.yml new file mode 100644 index 0000000..c1de4cd --- /dev/null +++ b/easy-dataset-main/.github/workflows/docker-build.yml @@ -0,0 +1,48 @@ +name: Build and Push Docker image on Tag + +on: + push: + tags: + - '*' + +jobs: + docker-image-release: + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository_owner }}/easy-dataset + tags: | + type=ref,event=tag + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/easy-dataset-main/.gitignore b/easy-dataset-main/.gitignore new file mode 100644 index 0000000..af76aed --- /dev/null +++ b/easy-dataset-main/.gitignore @@ -0,0 +1,22 @@ +node_modules +build +.vscode +website-local.json +ai-local.json +.next +.DS_Store +tsconfig.tsbuildinfo +mock-login-callback.ts +.env.local +/src/test/crawler +/src/test/mock +/test +/dist +/prisma/*.sqlite +.idea +!local-db/empty.txt +/local-db +prisma/local-db/db.sqlite +/local-db2 +.trae +opencode.json \ No newline at end of file diff --git a/easy-dataset-main/.husky/commit-msg b/easy-dataset-main/.husky/commit-msg new file mode 100644 index 0000000..08147d0 --- /dev/null +++ b/easy-dataset-main/.husky/commit-msg @@ -0,0 +1,3 @@ +#!/usr/bin/env sh + +npx commitlint --edit "$1" diff --git a/easy-dataset-main/.husky/pre-commit b/easy-dataset-main/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/easy-dataset-main/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/easy-dataset-main/.npmrc b/easy-dataset-main/.npmrc new file mode 100644 index 0000000..cb52cc8 --- /dev/null +++ b/easy-dataset-main/.npmrc @@ -0,0 +1,3 @@ +# 国内用户可使用淘宝源加速 (Chinese users can use Taobao registry for faster downloads) +# registry=https://registry.npmmirror.com +registry=https://registry.npmjs.org \ No newline at end of file diff --git a/easy-dataset-main/.prettierrc.js b/easy-dataset-main/.prettierrc.js new file mode 100644 index 0000000..af7bb09 --- /dev/null +++ b/easy-dataset-main/.prettierrc.js @@ -0,0 +1,13 @@ +module.exports = { + semi: true, + trailingComma: 'none', + singleQuote: true, + tabWidth: 2, + useTabs: false, + bracketSpacing: true, + arrowParens: 'avoid', + proseWrap: 'preserve', + jsxBracketSameLine: true, + printWidth: 120, + endOfLine: 'auto' +}; diff --git a/easy-dataset-main/.windsurfrules b/easy-dataset-main/.windsurfrules new file mode 100644 index 0000000..c4f19f0 --- /dev/null +++ b/easy-dataset-main/.windsurfrules @@ -0,0 +1,124 @@ +# Easy DataSet 项目架构设计 + +## 项目概述 + +Easy DataSet 是一个用于创建大模型微调数据集的应用程序。用户可以上传文本文件,系统会自动分割文本并生成问题,最终生成用于微调的数据集。 + +## 技术栈 + +- **前端框架**: Next.js 14 (App Router) +- **UI 框架**: Material-UI (MUI) +- **数据存储**: fs 文件系统模拟数据库 +- **开发语言**: JavaScript +- **依赖管理**: pnpm + +## 目录结构 + +``` +easy-dataset/ +├── app/ # Next.js 应用目录 +│ ├── api/ # API 路由 +│ │ └── projects/ # 项目相关 API +│ ├── projects/ # 项目相关页面 +│ │ ├── [projectId]/ # 项目详情页面 +│ └── page.js # 主页 +├── components/ # React 组件 +│ ├── home/ # 主页相关组件 +│ │ ├── HeroSection.js +│ │ ├── ProjectList.js +│ │ └── StatsCard.js +│ ├── Navbar.js # 导航栏组件 +│ └── CreateProjectDialog.js +├── lib/ # 工具库 +│ └── db/ # 数据库模块 +│ ├── base.js # 基础工具函数 +│ ├── projects.js # 项目管理 +│ ├── texts.js # 文本处理 +│ ├── datasets.js # 数据集管理 +│ └── index.js # 模块导出 +├── styles/ # 样式文件 +│ └── home.js # 主页样式 +└── local-db/ # 本地数据库目录 +``` + +## 核心模块设计 + +### 1. 数据库模块 (`lib/db/`) + +#### base.js +- 提供基础的文件操作功能 +- 确保数据库目录存在 +- 读写 JSON 文件的工具函数 + +#### projects.js +- 项目的 CRUD 操作 +- 项目配置管理 +- 项目目录结构维护 + +#### texts.js +- 文献处理功能 +- 文本片段存储和检索 +- 文件上传处理 + +#### datasets.js +- 数据集生成和管理 +- 问题列表管理 +- 标签树管理 + +### 2. 前端组件 (`components/`) + +#### Navbar.js +- 顶部导航栏 +- 项目切换 +- 模型选择 +- 主题切换 + +#### home/ 目录组件 +- HeroSection.js: 主页顶部展示区 +- ProjectList.js: 项目列表展示 +- StatsCard.js: 数据统计展示 +- CreateProjectDialog.js: 创建项目的对话框 + +### 3. 页面路由 (`app/`) + +#### 主页 (`page.js`) +- 项目列表展示 +- 创建项目入口 +- 数据统计展示 + +#### 项目详情页 (`projects/[projectId]/`) +- text-split/: 文献处理页面 +- questions/: 问题列表页面 +- datasets/: 数据集页面 +- settings/: 项目设置页面 + +#### API 路由 (`api/`) +- projects/: 项目管理 API +- texts/: 文本处理 API +- questions/: 问题生成 API +- datasets/: 数据集管理 API + +## 数据流设计 + +### 项目创建流程 +1. 用户通过主页或导航栏创建新项目 +2. 填写项目基本信息(名称、描述) +3. 系统创建项目目录和初始配置文件 +4. 重定向到项目详情页 + +### 文献处理流程 +1. 用户上传 Markdown 文件 +2. 系统保存原始文件到项目目录 +3. 调用文本分割服务,生成片段和目录结构 +4. 展示分割结果和提取的目录 + +### 问题生成流程 +1. 用户选择需要生成问题的文本片段 +2. 系统调用大模型API生成问题 +3. 保存问题到问题列表和标签树 + +### 数据集生成流程 +1. 用户选择需要生成答案的问题 +2. 系统调用大模型API生成答案 +3. 保存数据集结果 +4. 提供导出功能 diff --git a/easy-dataset-main/AGENTS.md b/easy-dataset-main/AGENTS.md new file mode 100644 index 0000000..f2857b1 --- /dev/null +++ b/easy-dataset-main/AGENTS.md @@ -0,0 +1,254 @@ +# Easy Dataset Agent 指南 + +## 项目概述 + +Easy Dataset 是一个专为大型语言模型(LLM)微调数据集创建而设计的应用程序。它提供完整的workflow,从文档处理到数据集导出,支持多种文件格式和AI模型。 + +## 技术栈 + +- **前端**: Next.js 14 (App Router), React 18, Material-UI v5 +- **后端**: Node.js, Prisma ORM, SQLite +- **AI集成**: OpenAI API, Ollama, 智谱AI, OpenRouter +- **桌面应用**: Electron +- **国际化**: i18next +- **构建工具**: npm/pnpm, Electron Builder + +## 核心架构 + +### 1. 数据流架构 + +``` +文档上传 → 文本分割 → 问题生成 → 答案生成 → 数据集导出 + ↓ ↓ ↓ ↓ ↓ +文件处理 智能分块 LLM生成 LLM生成 格式转换 +``` + +### 2. 模块结构 + +``` +lib/ +├── api/ # API接口层 +├── db/ # 数据访问层 +├── file/ # 文件处理模块 +├── llm/ # AI模型集成 +├── services/ # 业务逻辑层 +└── util/ # 工具函数 +``` + +## 开发指南 + +### 环境设置 + +```bash +# 安装依赖 +npm install + +# 数据库初始化 +npm run db:push + +# 开发模式 +npm run dev + +# 构建 +npm run build +``` + +### 代码规范 + +- 使用ES6+语法 +- 模块化开发 +- 异步操作使用async/await +- 错误处理使用try/catch +- 注释使用JSDoc格式 + +### 重要文件路径 + +- **主入口**: `app/page.js` +- **项目路由**: `app/projects/[projectId]/` +- **API路由**: `app/api/` +- **LLM核心**: `lib/llm/core/index.js` +- **任务处理**: `lib/services/tasks/` + +## 功能模块详解 + +### 1. 文档处理模块 (`lib/file/`) + +- **支持的格式**: PDF, Markdown, DOCX, EPUB, TXT +- **核心功能**: + - 智能文本分割 + - 目录结构提取 + - 自定义分隔符分块 + - 多语言支持 + +### 2. AI模型集成 (`lib/llm/`) + +- **支持的提供商**: + - OpenAI (GPT系列) + - Ollama (本地模型) + - 智谱AI (GLM系列) + - OpenRouter (多模型聚合) +- **功能特性**: + - 统一API接口 + - 流式输出支持 + - 多语言提示词 + - 错误重试机制 + +### 3. 任务系统 (`lib/services/tasks/`) + +- **任务类型**: + - 文件处理任务 + - 问题生成任务 + - 答案生成任务 + - 数据清洗任务 +- **状态管理**: 待处理、处理中、完成、失败 + +### 4. 数据管理 (`lib/db/`) + +- **数据模型**: + - Project (项目) + - Text/Chunk (文本块) + - Question (问题) + - Dataset (数据集) + - Tag (标签) + +## 常用开发任务 + +### 添加新的AI模型提供商 + +1. 在 `lib/llm/core/providers/` 创建新的provider文件 +2. 实现基础接口 (generate, streamGenerate) +3. 在 `lib/llm/core/index.js` 中注册provider +4. 更新配置文件和UI界面 + +### 添加新的文件格式支持 + +1. 在 `lib/file/file-process/` 创建格式处理器 +2. 实现内容提取和文本转换逻辑 +3. 更新文件类型检测和验证 +4. 添加相应的UI组件 + +### 自定义提示词模板 + +1. 在 `lib/llm/prompts/` 创建新的提示词文件 +2. 使用i18n支持多语言 +3. 在设置界面添加配置选项 +4. 测试不同模型的效果 + +### 添加新的导出格式 + +1. 在 `components/export/` 创建新的导出组件 +2. 实现数据格式转换逻辑 +3. 更新导出对话框界面 +4. 添加格式验证和错误处理 + +## 调试技巧 + +### 1. 数据库调试 + +```bash +# 打开Prisma Studio +npm run db:studio + +# 查看数据库文件 +sqlite3 prisma/db.sqlite +``` + +### 2. LLM API调试 + +```javascript +// 在lib/llm/core/index.js中添加日志 +console.log('LLM Request:', { provider, model, prompt }); +console.log('LLM Response:', response); +``` + +### 3. 文件处理调试 + +```javascript +// 在lib/file/中添加调试信息 +console.log('File processing:', fileName, fileType); +console.log('Text chunks:', chunks.length, chunks[0]); +``` + +## 性能优化建议 + +### 1. 文件处理优化 + +- 大文件分片处理 +- 异步并发处理 +- 内存使用监控 +- 进度条显示 + +### 2. LLM调用优化 + +- 请求缓存机制 +- 批量处理请求 +- 重试策略优化 +- 并发数控制 + +### 3. 前端性能优化 + +- 组件懒加载 +- 虚拟滚动列表 +- 图片懒加载 +- 代码分割 + +## 常见问题解决 + +### 1. 数据库相关问题 + +- **问题**: 数据库连接失败 +- **解决**: 检查prisma配置,确保数据库文件存在 + +### 2. LLM API相关问题 + +- **问题**: API调用超时 +- **解决**: 调整超时时间,检查网络连接,增加重试机制 + +### 3. 文件处理问题 + +- **问题**: 大文件处理内存溢出 +- **解决**: 使用流式处理,分块读取,增加内存限制 + +### 4. Electron打包问题 + +- **问题**: 打包后应用无法启动 +- **解决**: 检查依赖项配置,确保native模块正确打包 + +## 部署指南 + +### Docker部署 + +```bash +# 构建镜像 +docker build -t easy-dataset . + +# 运行容器 +docker run -d -p 1717:1717 -v ./local-db:/app/local-db easy-dataset +``` + +### 桌面应用构建 + +```bash +# 构建各平台安装包 +npm run electron-build-mac # macOS +npm run electron-build-win # Windows +npm run electron-build-linux # Linux +``` + +## 贡献指南 + +### 提交规范 + +- 使用conventional commits格式 +- 提交前运行lint检查 +- 更新相关文档 +- 添加测试用例 + +### 分支策略 + +- `main`: 主分支,稳定版本 +- `dev`: 开发分支,集成新功能 +- `feature/*`: 功能分支 +- `fix/*`: 修复分支 + +--- diff --git a/easy-dataset-main/ARCHITECTURE.md b/easy-dataset-main/ARCHITECTURE.md new file mode 100644 index 0000000..417ff30 --- /dev/null +++ b/easy-dataset-main/ARCHITECTURE.md @@ -0,0 +1,183 @@ +# Easy DataSet 项目架构设计 + +## 项目概述 + +Easy DataSet 是一个用于创建大模型微调数据集的应用程序。用户可以上传文本文件,系统会自动分割文本并生成问题,最终生成用于微调的数据集。 + +## 技术栈 + +- **前端框架**: Next.js 14 (App Router) +- **UI 框架**: Material-UI (MUI) +- **数据存储**: fs 文件系统模拟数据库 +- **开发语言**: JavaScript + +## 目录结构 + +``` +easy-dataset/ +├── app/ # Next.js 应用目录 +│ ├── api/ # API 路由 +│ │ └── projects/ # 项目相关 API +│ ├── projects/ # 项目相关页面 +│ │ ├── [projectId]/ # 项目详情页面 +│ └── page.js # 主页 +├── components/ # React 组件 +│ ├── home/ # 主页相关组件 +│ │ ├── HeroSection.js +│ │ ├── ProjectList.js +│ │ └── StatsCard.js +│ ├── Navbar.js # 导航栏组件 +│ └── CreateProjectDialog.js +├── lib/ # 工具库 +│ └── db/ # 数据库模块 +│ ├── base.js # 基础工具函数 +│ ├── projects.js # 项目管理 +│ ├── texts.js # 文本处理 +│ ├── datasets.js # 数据集管理 +│ └── index.js # 模块导出 +├── styles/ # 样式文件 +│ └── home.js # 主页样式 +└── local-db/ # 本地数据库目录 +``` + +## 核心模块设计 + +### 1. 数据库模块 (`lib/db/`) + +#### base.js + +- 提供基础的文件操作功能 +- 确保数据库目录存在 +- 读写 JSON 文件的工具函数 + +#### projects.js + +- 项目的 CRUD 操作 +- 项目配置管理 +- 项目目录结构维护 + +#### texts.js + +- 文献处理功能 +- 文本片段存储和检索 +- 文件上传处理 + +#### datasets.js + +- 数据集生成和管理 +- 问题列表管理 +- 标签树管理 + +### 2. 前端组件 (`components/`) + +#### Navbar.js + +- 顶部导航栏 +- 项目切换 +- 模型选择 +- 主题切换 + +#### home/ 目录组件 + +- HeroSection.js: 主页顶部展示区 +- ProjectList.js: 项目列表展示 +- StatsCard.js: 数据统计展示 +- CreateProjectDialog.js: 创建项目的对话框 + +### 3. 页面路由 (`app/`) + +#### 主页 (`page.js`) + +- 项目列表展示 +- 创建项目入口 +- 数据统计展示 + +#### 项目详情页 (`projects/[projectId]/`) + +- text-split/: 文献处理页面 +- questions/: 问题列表页面 +- datasets/: 数据集页面 +- settings/: 项目设置页面 + +#### API 路由 (`api/`) + +- projects/: 项目管理 API +- texts/: 文本处理 API +- questions/: 问题生成 API +- datasets/: 数据集管理 API + +## 数据流设计 + +### 项目创建流程 + +1. 用户通过主页或导航栏创建新项目 +2. 填写项目基本信息(名称、描述) +3. 系统创建项目目录和初始配置文件 +4. 重定向到项目详情页 + +### 文献处理流程 + +1. 用户上传 Markdown 文件 +2. 系统保存原始文件到项目目录 +3. 调用文本分割服务,生成片段和目录结构 +4. 展示分割结果和提取的目录 + +### 问题生成流程 + +1. 用户选择需要生成问题的文本片段 +2. 系统调用大模型API生成问题 +3. 保存问题到问题列表和标签树 + +### 数据集生成流程 + +1. 用户选择需要生成答案的问题 +2. 系统调用大模型API生成答案 +3. 保存数据集结果 +4. 提供导出功能 + +## 模型配置 + +支持多种大模型提供商配置: + +- Ollama +- OpenAI +- 硅基流动 +- 深度求索 +- 智谱AI + +每个提供商支持配置: + +- API 地址 +- API 密钥 +- 模型名称 + +## 未来扩展方向 + +1. 支持更多文件格式(PDF、DOC等) +2. 增加数据集质量评估功能 +3. 添加数据集版本管理 +4. 实现团队协作功能 +5. 增加更多数据集导出格式 + +## 国际化处理 + +### 技术选型 + +- **国际化库**: i18next + react-i18next +- **语言检测**: i18next-browser-languagedetector +- **支持语言**: 英文(en)、简体中文(zh-CN) + +### 目录结构 + +``` +easy-dataset/ +├── locales/ # 国际化资源目录 +│ ├── en/ # 英文翻译 +│ │ └── translation.json +│ ├── zh-CN/ # 中文翻译 +│ │ └── translation.json +│ └── pt-BR/ # 中文翻译 +│ └── translation.json +├── lib/ +│ └── i18n.js # i18next 配置 +``` diff --git a/easy-dataset-main/Dockerfile b/easy-dataset-main/Dockerfile new file mode 100644 index 0000000..b857162 --- /dev/null +++ b/easy-dataset-main/Dockerfile @@ -0,0 +1,86 @@ +# 创建包含pnpm的基础镜像 +FROM node:20-alpine AS pnpm-base +RUN npm install -g pnpm@9 + +# 构建阶段 +FROM pnpm-base AS builder +WORKDIR /app + +# 添加构建参数,用于识别目标平台 +ARG TARGETPLATFORM + +# 安装构建依赖 +RUN apk add --no-cache --virtual .build-deps \ + python3 \ + make \ + g++ \ + cairo-dev \ + pango-dev \ + jpeg-dev \ + giflib-dev \ + librsvg-dev \ + build-base \ + pixman-dev \ + pkgconfig + +# 复制依赖文件和npm配置并安装(.npmrc中可配置国内源加速) +COPY package.json pnpm-lock.yaml .npmrc ./ +RUN pnpm install + +# 复制源代码 +COPY . . + +# 根据目标平台设置Prisma二进制目标并构建应用 +RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ + echo "Configuring for ARM64 platform"; \ + sed -i 's/binaryTargets = \[.*\]/binaryTargets = \["linux-musl-arm64-openssl-3.0.x"\]/' prisma/schema.prisma; \ + PRISMA_CLI_BINARY_TARGETS="linux-musl-arm64-openssl-3.0.x" pnpm build; \ + else \ + echo "Configuring for AMD64 platform (default)"; \ + sed -i 's/binaryTargets = \[.*\]/binaryTargets = \["linux-musl-openssl-3.0.x"\]/' prisma/schema.prisma; \ + PRISMA_CLI_BINARY_TARGETS="linux-musl-openssl-3.0.x" pnpm build; \ + fi + +# 构建完成后移除开发依赖,只保留生产依赖 +RUN pnpm prune --prod + +# 运行阶段 +FROM pnpm-base AS runner +WORKDIR /app + +# 只安装运行时依赖 +RUN apk add --no-cache \ + cairo \ + pango \ + jpeg \ + giflib \ + librsvg \ + pixman + +# 复制package.json和.env文件 +COPY package.json .env ./ + +# 从构建阶段复制精简后的node_modules(只包含生产依赖) +COPY --from=builder /app/node_modules ./node_modules + +# 从构建阶段复制构建产物 +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/electron ./electron + +# 复制 prisma 到模板目录(用于自动初始化) +COPY --from=builder /app/prisma /app/prisma-template + +# 复制并设置 entrypoint 脚本(sed 去除 Windows 换行符 \r,防止 CRLF 导致 "no such file or directory") +COPY docker-entrypoint.sh /usr/local/bin/ +RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh && \ + chmod +x /usr/local/bin/docker-entrypoint.sh + +# 设置生产环境 +ENV NODE_ENV=production + +EXPOSE 1717 + +# 使用 entrypoint 脚本 +ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] +CMD ["pnpm", "start"] diff --git a/easy-dataset-main/LICENSE b/easy-dataset-main/LICENSE new file mode 100644 index 0000000..85fb8ec --- /dev/null +++ b/easy-dataset-main/LICENSE @@ -0,0 +1,40 @@ +GNU AFFERO GENERAL PUBLIC LICENSE +Version 3, 19 November 2007 + +Copyright (C) 2025 Easy Dataset Project + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see https://www.gnu.org/licenses/. + +Additional Terms for Easy Dataset: + +1. Contact Information +If you wish to use Easy Dataset under different terms, please contact the +copyright holders at: 1009903985@qq.com + +2. Branding Restrictions +You may not use the names "Easy Dataset" or "EasyDataset" to endorse or +promote products derived from this software without prior written permission. + +3. Disclaimer of Warranty +The software is provided "as is", without warranty of any kind, express or +implied, including but not limited to the warranties of merchantability, +fitness for a particular purpose and noninfringement. In no event shall the +authors or copyright holders be liable for any claim, damages or other +liability, whether in an action of contract, tort or otherwise, arising from, +out of or in connection with the software or the use or other dealings in the +software. + +4. Compliance with Laws +You are responsible for ensuring your use of the software complies with all +applicable laws, including but not limited to export control regulations. \ No newline at end of file diff --git a/easy-dataset-main/README.md b/easy-dataset-main/README.md new file mode 100644 index 0000000..2d1cb43 --- /dev/null +++ b/easy-dataset-main/README.md @@ -0,0 +1,294 @@ +
+ +![](./public//imgs/bg2.png) + +GitHub Repo stars +GitHub Downloads (all assets, all releases) +GitHub Release +AGPL 3.0 License +GitHub contributors +GitHub last commit + + arXiv:2507.04009 + + +ConardLi%2Feasy-dataset | Trendshift + +**A powerful tool for creating fine-tuning datasets for Large Language Models** + +[简体中文](./README.zh-CN.md) | [English](./README.md) | [Türkçe](./README.tr.md) + +[Features](#features) • [Quick Start](#local-run) • [Documentation](https://docs.easy-dataset.com/ed/en) • [Contributing](#contributing) • [License](#license) + +If you like this project, please give it a Star⭐️, or buy the author a coffee => [Donate](./public/imgs/aw.jpg) ❤️! + +
+ +## Overview + +Easy Dataset is an application specifically designed for building large language model (LLM) datasets. It features an intuitive interface, along with built-in powerful document parsing tools, intelligent segmentation algorithms, data cleaning and augmentation capabilities. The application can convert domain-specific documents in various formats into high-quality structured datasets, which are applicable to scenarios such as model fine-tuning, retrieval-augmented generation (RAG), and model performance evaluation. + +![](./public/imgs/arc3.png) + +## News + +🎉🎉 Easy Dataset Version 1.7.0 launches brand-new evaluation capabilities! You can effortlessly convert domain-specific documents into evaluation datasets (test sets) and automatically run multi-dimensional evaluation tasks. Additionally, it comes with a human blind test system, enabling you to easily meet needs such as vertical domain model evaluation, post-fine-tuning model performance assessment, and RAG recall rate evaluation. Tutorial: [https://www.bilibili.com/video/BV1CRrVB7Eb4/](https://www.bilibili.com/video/BV1CRrVB7Eb4/) + +## Features + +### 📄 Document Processing & Data Generation + +- **Intelligent Document Processing**: Supports PDF, Markdown, DOCX, TXT, EPUB and more formats with intelligent recognition +- **Intelligent Text Splitting**: Multiple splitting algorithms (Markdown structure, recursive separators, fixed length, code-aware chunking), with customizable visual segmentation +- **Intelligent Question Generation**: Auto-extract relevant questions from text segments, with question templates and batch generation +- **Domain Label Tree**: Intelligently builds global domain label trees based on document structure, with auto-tagging capabilities +- **Answer Generation**: Uses LLM API to generate comprehensive answers and Chain of Thought (COT), with AI optimization +- **Data Cleaning**: Intelligent text cleaning to remove noise and improve data quality + +### 🔄 Multiple Dataset Types + +- **Single-Turn QA Datasets**: Standard question-answer pairs for basic fine-tuning +- **Multi-Turn Dialogue Datasets**: Customizable roles and scenarios for conversational format +- **Image QA Datasets**: Generate visual QA data from images, with multiple import methods (directory, PDF, ZIP) +- **Data Distillation**: Generate label trees and questions directly from domain topics without uploading documents + +### 📊 Model Evaluation System + +- **Evaluation Datasets**: Generate true/false, single-choice, multiple-choice, short-answer, and open-ended questions +- **Automated Model Evaluation**: Use Judge Model to automatically evaluate model answer quality with customizable scoring rules +- **Human Blind Test (Arena)**: Double-blind comparison of two models' answers for unbiased evaluation +- **AI Quality Assessment**: Automatic quality scoring and filtering of generated datasets + +### 🛠️ Advanced Features + +- **Custom Prompts**: Project-level customization of all prompt templates (question generation, answer generation, data cleaning, etc.) +- **GA Pair Generation**: Genre-Audience pair generation to enrich data diversity +- **Task Management Center**: Background batch task processing with monitoring and interruption support +- **Resource Monitoring Dashboard**: Token consumption statistics, API call tracking, model performance analysis +- **Model Testing Playground**: Compare up to 3 models simultaneously + +### 📤 Export & Integration + +- **Multiple Export Formats**: Alpaca, ShareGPT, Multilingual-Thinking formats with JSON/JSONL file types +- **Balanced Export**: Configure export counts per tag for dataset balancing +- **LLaMA Factory Integration**: One-click LLaMA Factory configuration file generation +- **Hugging Face Upload**: Direct upload datasets to Hugging Face Hub + +### 🤖 Model Support + +- **Wide Model Compatibility**: Compatible with all LLM APIs that follow the OpenAI format +- **Multi-Provider Support**: OpenAI, Ollama (local models), Zhipu AI, Alibaba Bailian, OpenRouter, and more +- **Vision Models**: Support Gemini, Claude, etc. for PDF parsing and image QA + +### 🌐 User Experience + +- **User-Friendly Interface**: Modern, intuitive UI designed for both technical and non-technical users +- **Multi-Language Support**: Complete Chinese, English, Turkish and Portuguese language support 🇹🇷 +- **Dataset Square**: Discover and explore public dataset resources +- **Desktop Clients**: Available for Windows, macOS, and Linux + +## Quick Demo + +https://github.com/user-attachments/assets/6ddb1225-3d1b-4695-90cd-aa4cb01376a8 + +## Local Run + +### Download Client + + + + + + + + + + + + + +
+ Windows + + MacOS + + Linux +
+ + +
+ Setup.exe +
+
+ + +
+ Intel +
+
+ + +
+ M +
+
+ + +
+ AppImage +
+
+ +### Install with NPM + +1. Clone the repository: + +```bash + git clone https://github.com/ConardLi/easy-dataset.git + cd easy-dataset +``` + +2. Install dependencies: + +```bash + npm install +``` + +3. Start the development server: + +```bash + npm run build + + npm run start +``` + +4. Open your browser and visit `http://localhost:1717` + +### Using the Official Docker Image + +1. Clone the repository: + +```bash +git clone https://github.com/ConardLi/easy-dataset.git +cd easy-dataset +``` + +2. Modify the `docker-compose.yml` file: + +```yml +services: + easy-dataset: + image: ghcr.io/conardli/easy-dataset + container_name: easy-dataset + ports: + - '1717:1717' + volumes: + - ./local-db:/app/local-db + - ./prisma:/app/prisma + restart: unless-stopped +``` + +> **Note:** It is recommended to use the `local-db` and `prisma` folders in the current code repository directory as mount paths to maintain consistency with the database paths when starting via NPM. + +> **Note:** The database file will be automatically initialized on first startup, no need to manually run `npm run db:push`. + +3. Start with docker-compose: + +```bash +docker-compose up -d +``` + +4. Open a browser and visit `http://localhost:1717` + +### Building with a Local Dockerfile + +If you want to build the image yourself, use the Dockerfile in the project root directory: + +1. Clone the repository: + +```bash +git clone https://github.com/ConardLi/easy-dataset.git +cd easy-dataset +``` + +2. Build the Docker image: + +```bash +docker build -t easy-dataset . +``` + +3. Run the container: + +```bash +docker run -d \ + -p 1717:1717 \ + -v ./local-db:/app/local-db \ + -v ./prisma:/app/prisma \ + --name easy-dataset \ + easy-dataset +``` + +> **Note:** It is recommended to use the `local-db` and `prisma` folders in the current code repository directory as mount paths to maintain consistency with the database paths when starting via NPM. + +> **Note:** The database file will be automatically initialized on first startup, no need to manually run `npm run db:push`. + +4. Open a browser and visit `http://localhost:1717` + +## Documentation + +- View the demo video of this project: [Easy Dataset Demo Video](https://www.bilibili.com/video/BV1y8QpYGE57/) +- For detailed documentation on all features and APIs, visit our [Documentation Site](https://docs.easy-dataset.com/ed/en) +- View the paper of this project: [Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents](https://arxiv.org/abs/2507.04009v1) + +## Community Practice + +- [Complete test set generation and model evaluation with Easy Dataset](https://www.bilibili.com/video/BV1CRrVB7Eb4/) +- [Easy Dataset × LLaMA Factory: Enabling LLMs to Efficiently Learn Domain Knowledge](https://buaa-act.feishu.cn/wiki/GVzlwYcRFiR8OLkHbL6cQpYin7g) +- [Easy Dataset Practical Guide: How to Build High-Quality Datasets?](https://www.bilibili.com/video/BV1MRMnz1EGW) +- [Interpretation of Key Feature Updates in Easy Dataset](https://www.bilibili.com/video/BV1fyJhzHEb7/) +- [Foundation Models Fine-tuning Datasets: Basic Knowledge Popularization](https://docs.easy-dataset.com/zhi-shi-ke-pu) + +## Contributing + +We welcome contributions from the community! If you'd like to contribute to Easy Dataset, please follow these steps: + +1. Fork the repository +2. Create a new branch (`git checkout -b feature/amazing-feature`) +3. Make your changes +4. Commit your changes (`git commit -m 'Add some amazing feature'`) +5. Push to the branch (`git push origin feature/amazing-feature`) +6. Open a Pull Request (submit to the DEV branch) + +Please ensure that tests are appropriately updated and adhere to the existing coding style. + +## Join Discussion Group & Contact the Author + +https://docs.easy-dataset.com/geng-duo/lian-xi-wo-men + +## License + +This project is licensed under the AGPL 3.0 License - see the [LICENSE](LICENSE) file for details. + +## Citation + +If this work is helpful, please kindly cite as: + +```bibtex +@misc{miao2025easydataset, + title={Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents}, + author={Ziyang Miao and Qiyu Sun and Jingyuan Wang and Yuchen Gong and Yaowei Zheng and Shiqi Li and Richong Zhang}, + year={2025}, + eprint={2507.04009}, + archivePrefix={arXiv}, + primaryClass={cs.CL}, + url={https://arxiv.org/abs/2507.04009} +} +``` + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=ConardLi/easy-dataset&type=Date)](https://www.star-history.com/#ConardLi/easy-dataset&Date) + +
+ Built with ❤️ by ConardLi • Follow me: WeChat Official AccountBilibiliJuejinZhihuYoutube +
diff --git a/easy-dataset-main/README.tr.md b/easy-dataset-main/README.tr.md new file mode 100644 index 0000000..17087c6 --- /dev/null +++ b/easy-dataset-main/README.tr.md @@ -0,0 +1,319 @@ +
+ +![](./public//imgs/bg2.png) + +GitHub Repo stars +GitHub Downloads (all assets, all releases) +GitHub Release +AGPL 3.0 License +GitHub contributors +GitHub last commit + + arXiv:2507.04009 + + +ConardLi%2Feasy-dataset | Trendshift + +**Büyük Dil Modelleri için ince ayar veri setleri oluşturmak için güçlü bir araç** + +[简体中文](./README.zh-CN.md) | [English](./README.md) | [Türkçe](./README.tr.md) + +[Özellikler](#özellikler) • [Hızlı Başlangıç](#yerel-çalıştırma) • [Dokümantasyon](https://docs.easy-dataset.com/ed/en) • [Katkıda Bulunma](#katkıda-bulunma) • [Lisans](#lisans) + +Bu projeyi beğendiyseniz, lütfen bir Yıldız⭐️ verin veya yazara bir kahve ısmarlayın => [Bağış](./public/imgs/aw.jpg) ❤️! + +
+ +## Genel Bakış + +Easy Dataset, Büyük Dil Modelleri (LLM'ler) için özel olarak tasarlanmış ince ayar veri setleri oluşturmak için bir uygulamadır. Alana özgü dosyaları yüklemek, içeriği akıllıca bölmek, sorular oluşturmak ve model ince ayarı için yüksek kaliteli eğitim verileri üretmek için sezgisel bir arayüz sağlar. + +Easy Dataset ile alan bilgisini yapılandırılmış veri setlerine dönüştürebilir, OpenAI formatını takip eden tüm LLM API'leriyle uyumlu çalışabilir ve ince ayar sürecini basit ve verimli hale getirebilirsiniz. + +![](./public/imgs/arc3.png) + +## Özellikler + +- **Akıllı Belge İşleme**: PDF, Markdown, DOCX dahil birden fazla formatın akıllı tanınması ve işlenmesi desteği +- **Akıllı Metin Bölme**: Birden fazla akıllı metin bölme algoritması ve özelleştirilebilir görsel segmentasyon desteği +- **Akıllı Soru Üretimi**: Her metin bölümünden ilgili soruları çıkarır +- **Alan Etiketleri**: Veri setleri için global alan etiketlerini akıllıca oluşturur, küresel anlama yeteneklerine sahiptir +- **Cevap Üretimi**: Kapsamlı cevaplar ve Düşünce Zinciri (COT) oluşturmak için LLM API kullanır +- **Esnek Düzenleme**: Sürecin herhangi bir aşamasında soruları, cevapları ve veri setlerini düzenleyin +- **Çoklu Dışa Aktarma Formatları**: Veri setlerini çeşitli formatlarda (Alpaca, ShareGPT, çok dilli düşünme) ve dosya türlerinde (JSON, JSONL) dışa aktarın +- **Geniş Model Desteği**: OpenAI formatını takip eden tüm LLM API'leriyle uyumlu +- **Tam Türkçe Dil Desteği**: Tüm arayüz ve AI işlemleri için eksiksiz Türkçe çeviriler 🇹🇷 +- **Kullanıcı Dostu Arayüz**: Hem teknik hem de teknik olmayan kullanıcılar için tasarlanmış sezgisel kullanıcı arayüzü +- **Özel Sistem İstemleri**: Model yanıtlarını yönlendirmek için özel sistem istemleri ekleyin + +## Hızlı Demo + +https://github.com/user-attachments/assets/6ddb1225-3d1b-4695-90cd-aa4cb01376a8 + +## Yerel Çalıştırma + +### İstemciyi İndirin + + + + + + + + + + + + + +
+ Windows + + MacOS + + Linux +
+ + +
+ Setup.exe +
+
+ + +
+ Intel +
+
+ + +
+ M +
+
+ + +
+ AppImage +
+
+ +### NPM ile Kurulum + +```bash +npm install +npm run db:push +npm run dev +``` + +### Docker ile Kurulum + +```bash +docker-compose up -d +``` + +Ardından `http://localhost:1717` adresine gidin. + +## Desteklenen AI Sağlayıcıları + +Easy Dataset, aşağıdakiler dahil olmak üzere birden fazla AI sağlayıcısını destekler: + +- **OpenAI**: GPT-4, GPT-3.5-turbo ve diğer modeller +- **Ollama**: Yerel model çalıştırma +- **智谱AI (GLM)**: Çince modeller +- **OpenRouter**: Çoklu model aggregatör +- **Özel API Uç Noktaları**: OpenAI formatını takip eden herhangi bir API + +## Proje Yapısı + +``` +easy-dataset/ +├── app/ # Next.js uygulama yönlendiricisi +│ ├── api/ # API rotaları +│ ├── projects/ # Proje sayfaları +│ └── dataset-square/ # Veri seti galerisi +├── components/ # React bileşenleri +├── lib/ # Temel kütüphaneler +│ ├── llm/ # LLM entegrasyonu +│ ├── db/ # Veritabanı erişimi +│ ├── file/ # Dosya işleme +│ └── services/ # İş mantığı +├── locales/ # i18n çevirileri +│ ├── en/ # İngilizce +│ ├── zh-CN/ # Basitleştirilmiş Çince +│ └── tr/ # Türkçe +├── prisma/ # Veritabanı şeması +└── electron/ # Electron masaüstü uygulaması +``` + +## Kullanım Rehberi + +### 1. Proje Oluşturma + +İlk olarak, yeni bir proje oluşturun ve proje adını, açıklamasını ve diğer temel bilgileri yapılandırın. + +### 2. Dosya Yükleme + +Alana özgü belgelerinizi yükleyin. Desteklenen formatlar: + +- PDF +- Markdown (.md) +- Microsoft Word (.docx) +- EPUB +- Düz metin (.txt) + +### 3. Metin Bölme + +Dosyalar aşağıdaki yöntemlerle akıllıca bölünebilir: + +- Doğal dil işleme tabanlı semantik bölme +- Özel ayırıcılara dayalı bölme +- Karakter sayısına dayalı sabit boyutlu bölme +- Manuel görsel bölme + +### 4. Alan Etiketleri Oluşturma + +Sistem, belge içeriğine dayalı olarak otomatik olarak hiyerarşik alan etiketleri oluşturabilir ve iki seviyeyi destekler. + +### 5. Soru Üretimi + +Her metin bloğu için sistem: + +- İçeriğe dayalı alakalı sorular oluşturur +- Tür ve hedef kitle perspektifi sorgulamayı destekler +- Soru sayısını özelleştirme seçeneği sunar + +### 6. Cevap Üretimi + +Yapılandırılmış LLM API'si kullanarak: + +- Her soru için kapsamlı cevaplar oluşturur +- Düşünce Zinciri (COT) üretimini destekler +- Farklı cevap şablonları destekler + +### 7. Veri Seti Dışa Aktarma + +Veri setinizi çeşitli formatlarda dışa aktarın: + +- **Alpaca Format**: Basit talimat-takip formatı +- **ShareGPT Format**: Çok turlu konuşma formatı +- **Çok Dilli Düşünme**: COT ile genişletilmiş format +- **Özel Format**: Kendi JSON yapınızı tanımlayın + +Dışa aktarma hedefleri: + +- Yerel dosya sistemi +- Hugging Face Hub +- LLaMA Factory uyumluluğu + +## Gelişmiş Özellikler + +### Veri Damıtma + +Mevcut veri setlerinden yeni eğitim örnekleri oluşturun: + +- Soru damıtma: Mevcut soru-cevap çiftlerinden yeni sorular oluşturun +- Etiket damıtma: Otomatik etiket ve kategorizasyon oluşturma + +### Tür-Hedef Kitle (GA) Çiftleri + +Spesifik içerik stilleri ve hedef kitleler için veri setlerini uyarlayın: + +- Tür: Akademik, teknik, yaratıcı yazma, vb. +- Hedef Kitle: Yeni başlayanlar, uzmanlar, öğrenciler, vb. + +### Toplu İşlemler + +Birden fazla öğeye verimli bir şekilde işlem: + +- Toplu soru üretimi +- Toplu cevap üretimi +- Toplu veri seti dışa aktarma + +### Görev Yönetimi + +Tüm arka plan görevlerini izleyin ve yönetin: + +- Dosya işleme görevleri +- Soru üretim görevleri +- Cevap üretim görevleri +- Dışa aktarma görevleri + +## Yapılandırma + +### LLM API Yapılandırması + +Ayarlar sayfasında LLM API'nizi yapılandırın: + +1. **Sağlayıcı**: OpenAI, Ollama, 智谱AI veya özel seçin +2. **API Anahtarı**: API anahtarınızı girin (gerekirse) +3. **Model**: Kullanılacak modeli seçin +4. **Temel URL**: Özel API'ler için temel URL'yi ayarlayın + +### Görev Ayarları + +Görev yürütme parametrelerini özelleştirin: + +- Soru üretimi için eşzamanlılık +- Cevap üretimi için eşzamanlılık +- Varsayılan soru sayısı +- Varsayılan cevap şablonu + +### Özel İstemler + +Her görev türü için özel sistem istemleri ekleyin: + +- Soru üretim istemi +- Cevap üretim istemi +- Etiket üretim istemi +- Damıtma istemi + +## Katkıda Bulunma + +Katkılara hoş geldiniz! Lütfen şu adımları izleyin: + +1. Repo'yu fork edin +2. Bir özellik dalı oluşturun (`git checkout -b feature/amazing-feature`) +3. Değişikliklerinizi commit edin (`git commit -m 'Add some amazing feature'`) +4. Dala push edin (`git push origin feature/amazing-feature`) +5. Bir Pull Request açın + +## Lisans + +Bu proje AGPL-3.0 Lisansı altında lisanslanmıştır. Detaylar için [LICENSE](./LICENSE) dosyasına bakın. + +## İletişim + +- **GitHub Issues**: [Yeni bir sorun oluşturun](https://github.com/ConardLi/easy-dataset/issues) +- **Email**: lhj19950927@gmail.com +- **WeChat Grubu**: README'deki QR koduna bakın + +## Alıntı + +Bu aracı araştırmanızda kullanırsanız, lütfen şu şekilde alıntı yapın: + +```bibtex +@misc{easy-dataset-2025, + title={Easy Dataset: A Tool for Creating Fine-tuning Datasets for Large Language Models}, + author={Conard Li}, + year={2025}, + publisher={GitHub}, + howpublished={\url{https://github.com/ConardLi/easy-dataset}} +} +``` + +## Teşekkürler + +Bu proje aşağıdaki harika açık kaynak projelerini kullanır: + +- [Next.js](https://nextjs.org/) +- [React](https://reactjs.org/) +- [Material-UI](https://mui.com/) +- [Prisma](https://www.prisma.io/) +- [Electron](https://www.electronjs.org/) + +--- + +
+⭐️ Bu projeyi beğendiyseniz, lütfen bir yıldız verin! ⭐️ +
diff --git a/easy-dataset-main/README.zh-CN.md b/easy-dataset-main/README.zh-CN.md new file mode 100644 index 0000000..e118b62 --- /dev/null +++ b/easy-dataset-main/README.zh-CN.md @@ -0,0 +1,300 @@ +
+ +![](./public//imgs/bg2.png) + +GitHub Repo stars +GitHub Downloads (all assets, all releases) +GitHub Release +AGPL 3.0 License +GitHub contributors +GitHub last commit + + arXiv:2507.04009 + + +ConardLi%2Feasy-dataset | Trendshift + +**一个强大的大型语言模型微调数据集创建工具** + +[简体中文](./README.zh-CN.md) | [English](./README.md) + +[功能特点](#功能特点) • [快速开始](#本地运行) • [使用文档](https://docs.easy-dataset.com/) • [贡献](#贡献) • [许可证](#许可证) + +如果喜欢本项目,请给本项目留下 Star⭐️,或者请作者喝杯咖啡呀 => [打赏作者](./public/imgs/aw.jpg) ❤️! + +
+ +## 概述 + +Easy Dataset 是一个专为创建大型语言模型数据集而设计的应用程序。它提供了直观的界面,内置了强大的文档解析工具、智能分割算法、数据清洗和数据增强能力,可以将各种格式的领域文献转化为高质量结构化数据集,可用于模型微调、RAG、模型效果评估等场景。 + +![Easy Dataset 产品架构图](./public/imgs/arc3.png) + +## 新闻 + +🎉🎉 Easy Dataset 1.7.0 版本上线全新的评估能力,你可以轻松将领域文献转换为评估数据集(测试集),并且可以自动执行多维度评估任务,另外还配备人工盲测系统,可以轻松助你完成垂直领域模型评估、模型微调后效果评估、RAG 召回率评估等需求,使用教程: [https://www.bilibili.com/video/BV1CRrVB7Eb4/](https://www.bilibili.com/video/BV1CRrVB7Eb4/) + +## 功能特点 + +### 📄 文档处理与数据生成 + +- **智能文档处理**:支持 PDF、Markdown、DOCX、TXT、EPUB 等多种格式智能识别和处理 +- **智能文本分割**:支持多种智能文本分割算法(Markdown 结构、递归分隔符、固定长度、代码智能分块等),支持自定义可视化分段 +- **智能问题生成**:从每个文本片段中自动提取相关问题,支持问题模板和批量生成 +- **领域标签树**:基于文档目录智能构建全局领域标签树,具备全局理解和自动打标能力 +- **答案生成**:使用 LLM API 为每个问题生成全面的答案和思维链(COT),支持 AI 智能优化 +- **数据清洗**:智能清洗文本块内容,去除噪音数据,提升数据质量 + +### 🔄 多种数据集类型 + +- **单轮问答数据集**:标准的问答对格式,适合基础微调 +- **多轮对话数据集**:支持自定义角色和场景的多轮对话格式 +- **图片问答数据集**:基于图片生成视觉问答数据,支持多种导入方式(目录、PDF、压缩包) +- **数据蒸馏**:无需上传文档,直接从领域主题自动生成标签树和问题 + +### 📊 模型评估体系 + +- **评估数据集**:支持生成判断题、单选题、多选题、简答题、开放题等多种题型的评估测试集 +- **模型自动评估**:使用教师模型(Judge Model)自动评估模型回答质量,支持自定义评分规则 +- **人工盲测 (Arena)**:双盲对比两个模型的回答质量,消除偏见进行公正评判 +- **AI 质量评估**:对生成的数据集进行自动质量评分和筛选 + +### 🛠️ 高级功能 + +- **自定义提示词**:项目级自定义各类提示词模板(问题生成、答案生成、数据清洗等) +- **GA 组合生成**:文体-受众对生成,丰富数据多样性 +- **任务管理中心**:后台批量任务处理,支持任务监控和中断 +- **资源监控看板**:Token 消耗统计、调用次数追踪、模型性能分析 +- **模型测试 Playground**:支持最多 3 个模型同时对比测试 + +### 📤 导出与集成 + +- **多种导出格式**:支持 Alpaca、ShareGPT、Multilingual-Thinking 等格式,JSON/JSONL 文件类型 +- **平衡导出**:按标签配置导出数量,实现数据集均衡 +- **LLaMA Factory 集成**:一键生成 LLaMA Factory 配置文件 +- **Hugging Face 上传**:直接将数据集上传至 Hugging Face Hub + +### 🤖 模型支持 + +- **广泛的模型兼容**:兼容所有遵循 OpenAI 格式的 LLM API +- **多提供商支持**:OpenAI、Ollama(本地模型)、智谱 AI、阿里百炼、OpenRouter 等 +- **视觉模型**:支持 Gemini、Claude 等视觉模型用于 PDF 解析和图片问答 + +### 🌐 用户体验 + +- **用户友好界面**:为技术和非技术用户设计的现代化直观 UI +- **多语言支持**:完整的中英文界面支持 +- **数据集广场**:发现和探索各种公开数据集资源 +- **桌面客户端**:提供 Windows、macOS、Linux 桌面应用 + +## 快速演示 + +https://github.com/user-attachments/assets/6ddb1225-3d1b-4695-90cd-aa4cb01376a8 + +## 本地运行 + +### 下载客户端 + + + + + + + + + + + + + +
+ Windows + + MacOS + + Linux +
+ + +
+ Setup.exe +
+
+ + +
+ Intel +
+
+ + +
+ M +
+
+ + +
+ AppImage +
+
+ +### 使用 NPM 安装 + +1. 克隆仓库: + +```bash + git clone https://github.com/ConardLi/easy-dataset.git + cd easy-dataset +``` + +2. 安装依赖: + +```bash + npm install +``` + +3. 启动开发服务器: + +```bash + npm run build + + npm run start +``` + +4. 打开浏览器并访问 `http://localhost:1717` + +### 使用官方 Docker 镜像 + +1. 克隆仓库: + +```bash +git clone https://github.com/ConardLi/easy-dataset.git +cd easy-dataset +``` + +2. 更改 `docker-compose.yml` 文件: + +```yml +services: + easy-dataset: + image: ghcr.io/conardli/easy-dataset + container_name: easy-dataset + ports: + - '1717:1717' + volumes: + - ./local-db:/app/local-db + - ./prisma:/app/prisma + restart: unless-stopped +``` + +> **注意:** 建议直接使用当前代码仓库目录下的 `local-db` 和 `prisma` 文件夹作为挂载路径,这样可以和 NPM 启动时的数据库路径保持一致。 + +> **注意:** 数据库文件会在首次启动时自动初始化,无需手动执行 `npm run db:push`。 + +3. 使用 docker-compose 启动 + +```bash +docker-compose up -d +``` + +4. 打开浏览器并访问 `http://localhost:1717` + +### 使用本地 Dockerfile 构建 + +如果你想自行构建镜像,可以使用项目根目录中的 Dockerfile: + +1. 克隆仓库: + +```bash +git clone https://github.com/ConardLi/easy-dataset.git +cd easy-dataset +``` + +2. 构建 Docker 镜像: + +```bash +docker build -t easy-dataset . +``` + +3. 运行容器: + +```bash +docker run -d \ + -p 1717:1717 \ + -v ./local-db:/app/local-db \ + -v ./prisma:/app/prisma \ + --name easy-dataset \ + easy-dataset +``` + +> **注意:** 建议直接使用当前代码仓库目录下的 `local-db` 和 `prisma` 文件夹作为挂载路径,这样可以和 NPM 启动时的数据库路径保持一致。 + +> **注意:** 数据库文件会在首次启动时自动初始化,无需手动执行 `npm run db:push`。 + +4. 打开浏览器,访问 `http://localhost:1717` + +## 文档 + +- 有关所有功能和 API 的详细文档,请访问我们的 [文档站点](https://docs.easy-dataset.com/) +- 查看本项目的演示视频:[Easy Dataset 演示视频](https://www.bilibili.com/video/BV1y8QpYGE57/) +- 查看本项目的论文:[Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents](https://arxiv.org/abs/2507.04009v1) + +## 社区教程 + +- [使用 Easy Dataset 完成测试集生成和模型评估](https://www.bilibili.com/video/BV1CRrVB7Eb4/) +- [Easy Dataset × LLaMA Factory: 让大模型高效学习领域知识](https://buaa-act.feishu.cn/wiki/KY9xwTGs1iqHrRkjXBwcZP9WnL9) +- [Easy Dataset 使用实战: 如何构建高质量数据集?](https://www.bilibili.com/video/BV1MRMnz1EGW) +- [Easy Dataset 1.4 重点功能更新解读](https://www.bilibili.com/video/BV1fyJhzHEb7/) +- [Easy Dataset 1.6 重点功能更新解读](https://www.bilibili.com/video/BV1Rq1hBtEJa/) +- [大模型微调数据集: 基础知识科普](https://docs.easy-dataset.com/zhi-shi-ke-pu) +- [实战案例1:生成汽车图片识别数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-1-sheng-cheng-qi-che-tu-pian-shi-bie-shu-ju-ji) +- [实战案例2:评论情感分类数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-2-ping-lun-qing-gan-fen-lei-shu-ju-ji) +- [实战案例3:物理学多轮对话数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-3-wu-li-xue-duo-lun-dui-hua-shu-ju-ji) +- [实战案例4:AI 智能体安全数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-4ai-zhi-neng-ti-an-quan-shu-ju-ji) +- [实战案例5:从图文 PPT 中提取数据集](https://docs.easy-dataset.com/bo-ke/shi-zhan-an-li/an-li-5-cong-tu-wen-ppt-zhong-ti-qu-shu-ju-ji) + +## 贡献 + +我们欢迎社区的贡献!如果您想为 Easy Dataset 做出贡献,请按照以下步骤操作: + +1. Fork 仓库 +2. 创建新分支(`git checkout -b feature/amazing-feature`) +3. 进行更改 +4. 提交更改(`git commit -m '添加一些惊人的功能'`) +5. 推送到分支(`git push origin feature/amazing-feature`) +6. 打开 Pull Request(提交至 DEV 分支) + +请确保适当更新测试并遵守现有的编码风格。 + +## 加交流群 & 联系作者 + +https://docs.easy-dataset.com/geng-duo/lian-xi-wo-men + +## 许可证 + +本项目采用 AGPL 3.0 许可证 - 有关详细信息,请参阅 [LICENSE](LICENSE) 文件。 + +## 引用 + +如果您觉得此项目有帮助,请考虑以下列格式引用 + +```bibtex +@misc{miao2025easydataset, + title={Easy Dataset: A Unified and Extensible Framework for Synthesizing LLM Fine-Tuning Data from Unstructured Documents}, + author={Ziyang Miao and Qiyu Sun and Jingyuan Wang and Yuchen Gong and Yaowei Zheng and Shiqi Li and Richong Zhang}, + year={2025}, + eprint={2507.04009}, + archivePrefix={arXiv}, + primaryClass={cs.CL}, + url={https://arxiv.org/abs/2507.04009} +} +``` + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=ConardLi/easy-dataset&type=Date)](https://www.star-history.com/#ConardLi/easy-dataset&Date) + +
+ ConardLi 用 ❤️ 构建 • 关注我:公众号B站掘金知乎Youtube +
diff --git a/easy-dataset-main/app/api/check-update/route.js b/easy-dataset-main/app/api/check-update/route.js new file mode 100644 index 0000000..891733d --- /dev/null +++ b/easy-dataset-main/app/api/check-update/route.js @@ -0,0 +1,86 @@ +import { NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs'; + +// Get current version +function getCurrentVersion() { + try { + const packageJsonPath = path.join(process.cwd(), 'package.json'); + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + return packageJson.version; + } catch (error) { + console.error('Failed to read version from package.json:', String(error)); + return '1.0.0'; + } +} + +// Get latest version from GitHub +async function getLatestVersion() { + try { + const owner = 'ConardLi'; + const repo = 'easy-dataset'; + const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases/latest`); + + if (!response.ok) { + throw new Error(`GitHub API request failed: ${response.status}`); + } + + const data = await response.json(); + return data.tag_name.replace('v', ''); + } catch (error) { + console.error('Failed to fetch latest version:', String(error)); + return null; + } +} + +// Check for updates +export async function GET() { + try { + const currentVersion = getCurrentVersion(); + const latestVersion = await getLatestVersion(); + + if (!latestVersion) { + return NextResponse.json({ + hasUpdate: false, + currentVersion, + latestVersion: null, + error: 'Failed to fetch latest version' + }); + } + + // Simple semver-like comparison + const hasUpdate = compareVersions(latestVersion, currentVersion) > 0; + + return NextResponse.json({ + hasUpdate, + currentVersion, + latestVersion, + releaseUrl: hasUpdate ? `https://github.com/ConardLi/easy-dataset/releases/tag/v${latestVersion}` : null + }); + } catch (error) { + console.error('Failed to check for updates:', String(error)); + return NextResponse.json( + { + hasUpdate: false, + error: 'Failed to check for updates' + }, + { status: 500 } + ); + } +} + +// Simple version comparison +function compareVersions(a, b) { + const partsA = a.split('.').map(Number); + const partsB = b.split('.').map(Number); + + for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { + const numA = i < partsA.length ? partsA[i] : 0; + const numB = i < partsB.length ? partsB[i] : 0; + + if (numA > numB) return 1; + if (numA < numB) return -1; + } + + return 0; +} diff --git a/easy-dataset-main/app/api/llm/fetch-models/route.js b/easy-dataset-main/app/api/llm/fetch-models/route.js new file mode 100644 index 0000000..48addb2 --- /dev/null +++ b/easy-dataset-main/app/api/llm/fetch-models/route.js @@ -0,0 +1,75 @@ +import { NextResponse } from 'next/server'; +import axios from 'axios'; + +// Fetch model list from provider +export async function POST(request) { + try { + const { endpoint, providerId, apiKey } = await request.json(); + + if (!endpoint) { + return NextResponse.json({ error: 'Missing required parameter: endpoint' }, { status: 400 }); + } + + let url = endpoint.replace(/\/$/, ''); // Remove trailing slash + + // Handle Ollama endpoint + if (providerId === 'ollama') { + // Remove possible /v1 or other version suffix + url = url.replace(/\/v\d+$/, ''); + + // Append /api if missing + if (!url.includes('/api')) { + url += '/api'; + } + url += '/tags'; + } else { + url += '/models'; + } + + const headers = {}; + if (apiKey) { + headers.Authorization = `Bearer ${apiKey}`; + } + + const response = await axios.get(url, { headers }); + + // Format response per provider + let formattedModels = []; + if (providerId === 'ollama') { + // Ollama /api/tags format: { models: [{ name: 'model-name', ... }] } + if (response.data.models && Array.isArray(response.data.models)) { + formattedModels = response.data.models.map(item => ({ + modelId: item.name, + modelName: item.name, + providerId + })); + } + } else { + // Default handling (OpenAI-compatible) + if (response.data.data && Array.isArray(response.data.data)) { + formattedModels = response.data.data.map(item => ({ + modelId: item.id, + modelName: item.id, + providerId + })); + } + } + + return NextResponse.json(formattedModels); + } catch (error) { + console.error('Failed to fetch model list:', String(error)); + + // Handle known error shapes + if (error.response) { + if (error.response.status === 401) { + return NextResponse.json({ error: 'Invalid API key' }, { status: 401 }); + } + return NextResponse.json( + { error: `Failed to fetch model list: ${error.response.statusText}` }, + { status: error.response.status } + ); + } + + return NextResponse.json({ error: `Failed to fetch model list: ${error.message}` }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/llm/model/route.js b/easy-dataset-main/app/api/llm/model/route.js new file mode 100644 index 0000000..4ce5f9d --- /dev/null +++ b/easy-dataset-main/app/api/llm/model/route.js @@ -0,0 +1,39 @@ +import { NextResponse } from 'next/server'; +import { getLlmModelsByProviderId } from '@/lib/db/llm-models'; + +// Get LLM models +export async function GET(request) { + try { + const searchParams = request.nextUrl.searchParams; + let providerId = searchParams.get('providerId'); + if (!providerId) { + return NextResponse.json({ error: 'Invalid parameters' }, { status: 400 }); + } + const models = await getLlmModelsByProviderId(providerId); + if (!models) { + return NextResponse.json({ error: 'LLM provider not found' }, { status: 404 }); + } + return NextResponse.json(models); + } catch (error) { + console.error('Database query error:', String(error)); + return NextResponse.json({ error: 'Database query failed' }, { status: 500 }); + } +} + +// Sync latest model list +export async function POST(request) { + try { + const { newModels, providerId } = await request.json(); + const models = await getLlmModelsByProviderId(providerId); + const existingModelIds = models.map(model => model.modelId); + const diffModels = newModels.filter(item => !existingModelIds.includes(item.modelId)); + if (diffModels.length > 0) { + // return NextResponse.json(await createLlmModels(diffModels)); + return NextResponse.json({ message: 'No new models to insert' }, { status: 200 }); + } else { + return NextResponse.json({ message: 'No new models to insert' }, { status: 200 }); + } + } catch (error) { + return NextResponse.json({ error: 'Database insert failed' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/llm/ollama/models/route.js b/easy-dataset-main/app/api/llm/ollama/models/route.js new file mode 100644 index 0000000..348f971 --- /dev/null +++ b/easy-dataset-main/app/api/llm/ollama/models/route.js @@ -0,0 +1,26 @@ +import { NextResponse } from 'next/server'; + +const OllamaClient = require('@/lib/llm/core/providers/ollama'); + +// Force dynamic route to prevent static generation +export const dynamic = 'force-dynamic'; + +export async function GET(request) { + try { + // Read host and port from query params + const { searchParams } = new URL(request.url); + const host = searchParams.get('host') || '127.0.0.1'; + const port = searchParams.get('port') || '11434'; + + // Create Ollama API client + const ollama = new OllamaClient({ + endpoint: `http://${host}:${port}/api` + }); + // Fetch model list + const models = await ollama.getModels(); + return NextResponse.json(models); + } catch (error) { + // console.error('fetch Ollama models error:', error); + return NextResponse.json({ error: 'fetch Models failed' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/llm/providers/route.js b/easy-dataset-main/app/api/llm/providers/route.js new file mode 100644 index 0000000..6dddead --- /dev/null +++ b/easy-dataset-main/app/api/llm/providers/route.js @@ -0,0 +1,14 @@ +import { NextResponse } from 'next/server'; +import { getLlmProviders } from '@/lib/db/llm-providers'; +import { sortProvidersByPriority } from '@/lib/util/providerLogo'; + +// Get LLM provider data +export async function GET() { + try { + const result = await getLlmProviders(); + return NextResponse.json(sortProvidersByPriority(result, item => item.id)); + } catch (error) { + console.error('Database query error:', String(error)); + return NextResponse.json({ error: 'Database query failed' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/monitoring/logs/route.js b/easy-dataset-main/app/api/monitoring/logs/route.js new file mode 100644 index 0000000..660bfc5 --- /dev/null +++ b/easy-dataset-main/app/api/monitoring/logs/route.js @@ -0,0 +1,107 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; + +export const dynamic = 'force-dynamic'; + +export async function GET(request) { + try { + const { searchParams } = new URL(request.url); + const timeRange = searchParams.get('timeRange') || '7d'; + const projectId = searchParams.get('projectId'); + const provider = searchParams.get('provider'); + const status = searchParams.get('status'); + const page = parseInt(searchParams.get('page') || '1', 10); + const pageSize = parseInt(searchParams.get('pageSize') || '10', 10); + const searchTerm = searchParams.get('search') || ''; + + let startDate = new Date(); + + if (timeRange === '24h') { + startDate.setHours(startDate.getHours() - 24); + } else if (timeRange === '30d') { + startDate.setDate(startDate.getDate() - 30); + } else { + startDate.setDate(startDate.getDate() - 7); + } + + const where = { + createAt: { + gte: startDate + } + }; + + if (projectId && projectId !== 'all') { + where.projectId = projectId; + } + if (provider && provider !== 'all') { + where.provider = provider; + } + if (status && status !== 'all') { + where.status = status; + } + + if (searchTerm) { + where.OR = [{ model: { contains: searchTerm } }, { errorMessage: { contains: searchTerm } }]; + } + + const total = await db.llmUsageLogs.count({ where }); + const logs = await db.llmUsageLogs.findMany({ + where, + select: { + id: true, + projectId: true, + provider: true, + model: true, + inputTokens: true, + outputTokens: true, + totalTokens: true, + latency: true, + status: true, + errorMessage: true, + createAt: true + }, + orderBy: { + createAt: 'desc' + }, + skip: (page - 1) * pageSize, + take: pageSize + }); + + const projectIds = [...new Set(logs.map(log => log.projectId))]; + const projects = await db.projects.findMany({ + where: { id: { in: projectIds } }, + select: { id: true, name: true } + }); + const projectMap = projects.reduce((acc, p) => { + acc[p.id] = p.name; + return acc; + }, {}); + + const details = logs.map(log => ({ + id: log.id, + projectId: log.projectId, + projectName: projectMap[log.projectId] || 'Unknown Project', + provider: log.provider, + model: log.model, + status: log.status, + failureReason: log.errorMessage, + inputTokens: log.inputTokens, + outputTokens: log.outputTokens, + totalTokens: log.totalTokens, + calls: 1, // Single record + avgLatency: log.status === 'SUCCESS' ? (log.latency / 1000).toFixed(2) + 's' : '-', + createAt: log.createAt + })); + + return NextResponse.json({ + details, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize) + }); + } catch (error) { + console.error('Failed to fetch monitoring logs:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/monitoring/stats/route.js b/easy-dataset-main/app/api/monitoring/stats/route.js new file mode 100644 index 0000000..f8d4619 --- /dev/null +++ b/easy-dataset-main/app/api/monitoring/stats/route.js @@ -0,0 +1,188 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; + +export const dynamic = 'force-dynamic'; + +export async function GET(request) { + try { + const { searchParams } = new URL(request.url); + const timeRange = searchParams.get('timeRange') || '7d'; // 24h, 7d, 30d + const projectId = searchParams.get('projectId'); + const provider = searchParams.get('provider'); + const status = searchParams.get('status'); + + let startDate = new Date(); + + if (timeRange === '24h') { + startDate.setHours(startDate.getHours() - 24); + } else if (timeRange === '30d') { + startDate.setDate(startDate.getDate() - 30); + } else { + startDate.setDate(startDate.getDate() - 7); + } + + const where = { + createAt: { + gte: startDate + } + }; + + if (projectId && projectId !== 'all') { + where.projectId = projectId; + } + if (provider && provider !== 'all') { + where.provider = provider; + } + if (status && status !== 'all') { + where.status = status; + } + + // 1. Fetch data for aggregation + // Note: Prisma aggregation can be slow on very large datasets. If needed, optimize with pre-aggregated tables. + const logs = await db.llmUsageLogs.findMany({ + where, + select: { + id: true, + projectId: true, + provider: true, + model: true, + inputTokens: true, + outputTokens: true, + totalTokens: true, + latency: true, + status: true, + errorMessage: true, + createAt: true, + dateString: true + }, + orderBy: { + createAt: 'desc' + } + }); + + // Build project name map + const projects = await db.projects.findMany({ + select: { id: true, name: true } + }); + const projectMap = projects.reduce((acc, p) => { + acc[p.id] = p.name; + return acc; + }, {}); + + // 2. Process and aggregate + const summary = { + totalTokens: 0, + inputTokens: 0, + outputTokens: 0, + totalCalls: logs.length, + successCalls: 0, + failedCalls: 0, + totalLatency: 0, + avgLatency: 0 + }; + + const trendMap = {}; + const modelStats = {}; + const detailedStatsMap = {}; // Key: projectId-model-status-errorMessage + + logs.forEach(log => { + // Summary + summary.totalTokens += log.totalTokens; + summary.inputTokens += log.inputTokens; + summary.outputTokens += log.outputTokens; + + if (log.status === 'SUCCESS') { + summary.successCalls++; + summary.totalLatency += log.latency; + } else { + summary.failedCalls++; + } + + // Trend (by day or hour) + let timeKey; + if (timeRange === '24h') { + const date = new Date(log.createAt); + timeKey = `${String(date.getHours()).padStart(2, '0')}:00`; + } else { + timeKey = log.dateString.slice(5); // MM-DD + } + + if (!trendMap[timeKey]) { + trendMap[timeKey] = { name: timeKey, input: 0, output: 0 }; + } + trendMap[timeKey].input += log.inputTokens; + trendMap[timeKey].output += log.outputTokens; + + // Model Distribution + const modelKey = log.model; + if (!modelStats[modelKey]) { + modelStats[modelKey] = { name: modelKey, value: 0 }; + } + modelStats[modelKey].value += log.totalTokens; + + // Detailed Table Aggregation + // Key: projectId + model + status + (errorMessage || '') + const errorKey = log.errorMessage || ''; + const detailKey = `${log.projectId}|${log.model}|${log.status}|${errorKey}`; + + if (!detailedStatsMap[detailKey]) { + detailedStatsMap[detailKey] = { + projectId: log.projectId, + projectName: projectMap[log.projectId] || 'Unknown Project', + provider: log.provider, + model: log.model, + status: log.status, + failureReason: log.errorMessage, + inputTokens: 0, + outputTokens: 0, + totalTokens: 0, + calls: 0, + totalLatency: 0 + }; + } + const detailItem = detailedStatsMap[detailKey]; + detailItem.inputTokens += log.inputTokens; + detailItem.outputTokens += log.outputTokens; + detailItem.totalTokens += log.totalTokens; + detailItem.calls += 1; + if (log.status === 'SUCCESS') { + detailItem.totalLatency += log.latency; + } + }); + + // Calculate averages + if (summary.successCalls > 0) { + summary.avgLatency = Math.round(summary.totalLatency / summary.successCalls); + } + summary.avgTokensPerCall = summary.totalCalls > 0 ? Math.round(summary.totalTokens / summary.totalCalls) : 0; + summary.failureRate = summary.totalCalls > 0 ? summary.failedCalls / summary.totalCalls : 0; + + // Format chart data + const trend = Object.values(trendMap).sort((a, b) => { + // Simple sorting; for production use, consider stricter time ordering. + return a.name.localeCompare(b.name); + }); + + const modelDistribution = Object.values(modelStats).sort((a, b) => b.value - a.value); + + // Format detailed table data + const details = Object.values(detailedStatsMap) + .map(item => ({ + ...item, + avgLatency: + item.status === 'SUCCESS' && item.calls > 0 ? (item.totalLatency / item.calls / 1000).toFixed(2) + 's' : '-' + })) + .sort((a, b) => b.totalTokens - a.totalTokens); // Default sorting by token usage + + return NextResponse.json({ + summary, + trend, + modelDistribution, + details, + projects + }); + } catch (error) { + console.error('Failed to fetch monitoring stats:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/monitoring/summary/route.js b/easy-dataset-main/app/api/monitoring/summary/route.js new file mode 100644 index 0000000..2987743 --- /dev/null +++ b/easy-dataset-main/app/api/monitoring/summary/route.js @@ -0,0 +1,132 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; + +export const dynamic = 'force-dynamic'; + +export async function GET(request) { + try { + const { searchParams } = new URL(request.url); + const timeRange = searchParams.get('timeRange') || '7d'; + const projectId = searchParams.get('projectId'); + const provider = searchParams.get('provider'); + const status = searchParams.get('status'); + + let startDate = new Date(); + + if (timeRange === '24h') { + startDate.setHours(startDate.getHours() - 24); + } else if (timeRange === '30d') { + startDate.setDate(startDate.getDate() - 30); + } else { + startDate.setDate(startDate.getDate() - 7); + } + + const where = { + createAt: { + gte: startDate + } + }; + + if (projectId && projectId !== 'all') { + where.projectId = projectId; + } + if (provider && provider !== 'all') { + where.provider = provider; + } + if (status && status !== 'all') { + where.status = status; + } + + const logs = await db.llmUsageLogs.findMany({ + where, + select: { + inputTokens: true, + outputTokens: true, + totalTokens: true, + latency: true, + status: true, + createAt: true, + dateString: true, + model: true + } + }); + + const summary = { + totalTokens: 0, + inputTokens: 0, + outputTokens: 0, + totalCalls: logs.length, + successCalls: 0, + failedCalls: 0, + totalLatency: 0, + avgLatency: 0 + }; + + const trendMap = {}; + const modelStats = {}; + + logs.forEach(log => { + summary.totalTokens += log.totalTokens; + summary.inputTokens += log.inputTokens; + summary.outputTokens += log.outputTokens; + + if (log.status === 'SUCCESS') { + summary.successCalls++; + summary.totalLatency += log.latency; + } else { + summary.failedCalls++; + } + + let timeKey; + if (timeRange === '24h') { + const date = new Date(log.createAt); + timeKey = `${String(date.getHours()).padStart(2, '0')}:00`; + } else { + timeKey = log.dateString.slice(5); + } + + if (!trendMap[timeKey]) { + trendMap[timeKey] = { name: timeKey, input: 0, output: 0 }; + } + trendMap[timeKey].input += log.inputTokens; + trendMap[timeKey].output += log.outputTokens; + + const modelKey = log.model; + if (!modelStats[modelKey]) { + modelStats[modelKey] = { name: modelKey, value: 0 }; + } + modelStats[modelKey].value += log.totalTokens; + }); + + if (summary.successCalls > 0) { + summary.avgLatency = Math.round(summary.totalLatency / summary.successCalls); + } + summary.avgTokensPerCall = summary.totalCalls > 0 ? Math.round(summary.totalTokens / summary.totalCalls) : 0; + summary.failureRate = summary.totalCalls > 0 ? summary.failedCalls / summary.totalCalls : 0; + + const trend = Object.values(trendMap).sort((a, b) => a.name.localeCompare(b.name)); + const modelDistribution = Object.values(modelStats).sort((a, b) => b.value - a.value); + + const projects = await db.projects.findMany({ + select: { id: true, name: true }, + orderBy: { createAt: 'desc' } + }); + + const allLogs = await db.llmUsageLogs.findMany({ + select: { provider: true }, + distinct: ['provider'] + }); + const providers = allLogs.map(log => log.provider).filter(Boolean); + + return NextResponse.json({ + summary, + trend, + modelDistribution, + projects, + providers + }); + } catch (error) { + console.error('Failed to fetch monitoring summary:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/batch-add-manual-ga/route.js b/easy-dataset-main/app/api/projects/[projectId]/batch-add-manual-ga/route.js new file mode 100644 index 0000000..1b945a9 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/batch-add-manual-ga/route.js @@ -0,0 +1,176 @@ +import { NextResponse } from 'next/server'; +import { getUploadFileInfoById } from '@/lib/db/upload-files'; +import { createGaPairs, getGaPairsByFileId } from '@/lib/db/ga-pairs'; + +/** + * 批量手动添加 GA 对到多个文件 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + const { fileIds, gaPair, appendMode = false } = body; + + if (!fileIds || !Array.isArray(fileIds) || fileIds.length === 0) { + return NextResponse.json({ error: 'File IDs array is required' }, { status: 400 }); + } + + if (!gaPair || !gaPair.genreTitle || !gaPair.audienceTitle) { + return NextResponse.json({ error: 'GA pair with genreTitle and audienceTitle is required' }, { status: 400 }); + } + + console.log('开始处理批量手动添加GA对请求'); + console.log('项目ID:', projectId); + console.log('请求的文件IDs:', fileIds); + console.log('GA对:', gaPair); + + // 使用 getUploadFileInfoById 逐个验证文件 + const validFiles = []; + const invalidFileIds = []; + + for (const fileId of fileIds) { + try { + console.log(`正在验证文件: ${fileId}`); + const fileInfo = await getUploadFileInfoById(fileId); + + if (fileInfo && fileInfo.projectId === projectId) { + console.log(`文件验证成功: ${fileInfo.fileName}`); + validFiles.push(fileInfo); + } else if (fileInfo) { + console.log(`文件属于其他项目: ${fileInfo.projectId} != ${projectId}`); + invalidFileIds.push(fileId); + } else { + console.log(`文件不存在: ${fileId}`); + invalidFileIds.push(fileId); + } + } catch (error) { + console.error(`验证文件 ${fileId} 时出错:`, String(error)); + invalidFileIds.push(fileId); + } + } + + console.log(`文件验证完成: 有效${validFiles.length}个, 无效${invalidFileIds.length}个`); + + if (validFiles.length === 0) { + return NextResponse.json( + { + error: 'No valid files found', + debug: { + projectId, + requestedIds: fileIds, + invalidIds: invalidFileIds, + message: 'None of the requested files belong to this project or exist in the database' + } + }, + { status: 404 } + ); + } + + // 批量手动添加 GA 对 + console.log('开始批量手动添加GA对...'); + console.log('追加模式:', appendMode); + const results = []; + + for (const file of validFiles) { + try { + console.log(`处理文件: ${file.fileName}`); + + // 检查是否已存在 GA 对 + const existingPairs = await getGaPairsByFileId(file.id); + + let pairNumber = 1; + if (appendMode && existingPairs && existingPairs.length > 0) { + // 追加模式:在现有 GA 对后面添加 + pairNumber = existingPairs.length + 1; + } else if (!appendMode && existingPairs && existingPairs.length > 0) { + // 非追加模式:如果已存在 GA 对则跳过 + console.log(`文件 ${file.fileName} 已存在GA对,跳过`); + results.push({ + fileId: file.id, + fileName: file.fileName, + success: true, + skipped: true, + message: 'GA pairs already exist' + }); + continue; + } + + // 创建 GA 对数据 + const gaPairData = [ + { + projectId, + fileId: file.id, + pairNumber, + genreTitle: gaPair.genreTitle.trim(), + genreDesc: gaPair.genreDesc?.trim() || '', + audienceTitle: gaPair.audienceTitle.trim(), + audienceDesc: gaPair.audienceDesc?.trim() || '', + isActive: true + } + ]; + + // 保存 GA 对 + if (appendMode) { + // 追加模式:只创建新的 GA 对 + await createGaPairs(gaPairData); + } else { + // 非追加模式:使用 saveGaPairs 替换现有的 + const { saveGaPairs } = await import('@/lib/db/ga-pairs'); + await saveGaPairs(projectId, file.id, [ + { + genre: { title: gaPair.genreTitle.trim(), description: gaPair.genreDesc?.trim() || '' }, + audience: { title: gaPair.audienceTitle.trim(), description: gaPair.audienceDesc?.trim() || '' } + } + ]); + } + + results.push({ + fileId: file.id, + fileName: file.fileName, + success: true, + skipped: false, + message: 'GA pair added successfully' + }); + + console.log(`成功为文件 ${file.fileName} 添加GA对`); + } catch (error) { + console.error(`为文件 ${file.fileName} 添加GA对失败:`, error); + results.push({ + fileId: file.id, + fileName: file.fileName, + success: false, + skipped: false, + error: error.message, + message: `Failed: ${error.message}` + }); + } + } + + // 统计结果 + const successCount = results.filter(r => r.success).length; + const failureCount = results.filter(r => !r.success).length; + + console.log(`批量手动添加完成: 成功${successCount}个, 失败${failureCount}个`); + + return NextResponse.json({ + success: true, + data: results, + summary: { + total: results.length, + success: successCount, + failure: failureCount, + processed: validFiles.length, + skipped: invalidFileIds.length + }, + message: `Added GA pairs to ${successCount} files, ${failureCount} failed, ${invalidFileIds.length} files not found` + }); + } catch (error) { + console.error('Error batch adding manual GA pairs:', String(error)); + return NextResponse.json({ error: String(error) || 'Failed to batch add manual GA pairs' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/batch-delete-files/route.js b/easy-dataset-main/app/api/projects/[projectId]/batch-delete-files/route.js new file mode 100644 index 0000000..989a094 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/batch-delete-files/route.js @@ -0,0 +1,196 @@ +import { NextResponse } from 'next/server'; +import { getUploadFileInfoById, delUploadFileInfoById } from '@/lib/db/upload-files'; +import { getProject } from '@/lib/db/projects'; +import { getProjectChunks, getProjectTocByName } from '@/lib/file/text-splitter'; +import { batchSaveTags } from '@/lib/db/tags'; +import { handleDomainTree } from '@/lib/util/domain-tree'; +import path from 'path'; +import { getProjectRoot } from '@/lib/db/base'; +import { promises as fs } from 'fs'; + +/** + * 批量删除文件 + * 复用单个文件删除的完整逻辑,包括领域树修订 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + const { fileIds, domainTreeAction = 'keep', model, language = '中文' } = body; + + if (!fileIds || !Array.isArray(fileIds) || fileIds.length === 0) { + return NextResponse.json({ error: 'File IDs array is required' }, { status: 400 }); + } + + console.log('开始处理批量删除文件请求'); + console.log('项目ID:', projectId); + console.log('请求的文件IDs:', fileIds); + console.log('领域树操作:', domainTreeAction); + + // 获取项目信息 + const project = await getProject(projectId); + if (!project) { + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + + // 验证文件并删除 + const results = []; + const deletedTocs = []; + let deletedCount = 0; + let failedCount = 0; + let totalStats = { + deletedChunks: 0, + deletedQuestions: 0, + deletedDatasets: 0 + }; + + for (const fileId of fileIds) { + try { + console.log(`正在验证文件: ${fileId}`); + const fileInfo = await getUploadFileInfoById(fileId); + + if (!fileInfo) { + console.log(`文件不存在: ${fileId}`); + results.push({ + fileId, + success: false, + error: 'File not found' + }); + failedCount++; + continue; + } + + if (fileInfo.projectId !== projectId) { + console.log(`文件属于其他项目: ${fileInfo.projectId} != ${projectId}`); + results.push({ + fileId, + success: false, + error: 'File belongs to another project' + }); + failedCount++; + continue; + } + + // 删除文件及其相关的文本块、问题和数据集 + console.log(`删除文件: ${fileInfo.fileName}`); + const { stats, fileName } = await delUploadFileInfoById(fileId); + + // 累计统计信息 + totalStats.deletedChunks += stats.deletedChunks || 0; + totalStats.deletedQuestions += stats.deletedQuestions || 0; + totalStats.deletedDatasets += stats.deletedDatasets || 0; + + // 获取并保存删除的 TOC 信息 + const deleteToc = await getProjectTocByName(projectId, fileName); + if (deleteToc) { + deletedTocs.push(deleteToc); + } + + // 删除 TOC 文件 + try { + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + const tocDir = path.join(projectPath, 'toc'); + const baseName = path.basename(fileInfo.fileName, path.extname(fileInfo.fileName)); + const tocPath = path.join(tocDir, `${baseName}-toc.json`); + await fs.unlink(tocPath); + console.log(`成功删除 TOC 文件: ${tocPath}`); + } catch (error) { + console.error(`删除 TOC 文件失败:`, String(error)); + } + + results.push({ + fileId, + fileName: fileInfo.fileName, + success: true, + stats + }); + deletedCount++; + + console.log(`成功删除文件: ${fileInfo.fileName}`); + } catch (error) { + console.error(`删除文件 ${fileId} 时出错:`, error); + results.push({ + fileId, + success: false, + error: error.message + }); + failedCount++; + } + } + + console.log(`批量删除完成: 成功${deletedCount}个, 失败${failedCount}个`); + + // 如果选择了保持领域树不变,直接返回删除结果 + if (domainTreeAction === 'keep') { + return NextResponse.json({ + success: true, + deletedCount, + failedCount, + total: fileIds.length, + results, + stats: totalStats, + domainTreeAction: 'keep', + message: `Successfully deleted ${deletedCount} files, ${failedCount} failed` + }); + } + + // 处理领域树更新 + try { + // 获取项目的所有文件 + const { chunks, toc } = await getProjectChunks(projectId); + + // 如果不存在文本块,说明项目已经没有文件了 + if (!chunks || chunks.length === 0) { + // 清空领域树 + await batchSaveTags(projectId, []); + return NextResponse.json({ + success: true, + deletedCount, + failedCount, + total: fileIds.length, + results, + stats: totalStats, + domainTreeAction, + message: `Successfully deleted ${deletedCount} files, domain tree cleared`, + domainTreeCleared: true + }); + } + + // 调用领域树处理模块 + await handleDomainTree({ + projectId, + action: domainTreeAction, + allToc: toc, + model: model, + language, + deleteToc: deletedTocs.length > 0 ? deletedTocs : undefined, + project + }); + + console.log('领域树更新成功'); + } catch (error) { + console.error('Error updating domain tree after batch deletion:', String(error)); + // 即使领域树更新失败,也不影响文件删除的结果 + } + + return NextResponse.json({ + success: true, + deletedCount, + failedCount, + total: fileIds.length, + results, + stats: totalStats, + domainTreeAction, + message: `Successfully deleted ${deletedCount} files, ${failedCount} failed` + }); + } catch (error) { + console.error('Error batch deleting files:', String(error)); + return NextResponse.json({ error: String(error) || 'Failed to batch delete files' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/batch-generateGA/route.js b/easy-dataset-main/app/api/projects/[projectId]/batch-generateGA/route.js new file mode 100644 index 0000000..8577157 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/batch-generateGA/route.js @@ -0,0 +1,106 @@ +import { NextResponse } from 'next/server'; +import { batchGenerateGaPairs } from '@/lib/services/ga/ga-pairs'; +import { getUploadFileInfoById } from '@/lib/db/upload-files'; // 导入单个文件查询函数 + +/** + * 批量生成多个文件的 GA 对 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + const { fileIds, modelConfigId, language = '中文', appendMode = false } = body; + + if (!fileIds || !Array.isArray(fileIds) || fileIds.length === 0) { + return NextResponse.json({ error: 'File IDs array is required' }, { status: 400 }); + } + + if (!modelConfigId) { + return NextResponse.json({ error: 'Model configuration ID is required' }, { status: 400 }); + } + + console.log('开始处理批量生成GA对请求'); + console.log('项目ID:', projectId); + console.log('请求的文件IDs:', fileIds); + + // 使用 getUploadFileInfoById 逐个验证文件 + const validFiles = []; + const invalidFileIds = []; + + for (const fileId of fileIds) { + try { + console.log(`正在验证文件: ${fileId}`); + const fileInfo = await getUploadFileInfoById(fileId); + + if (fileInfo && fileInfo.projectId === projectId) { + console.log(`文件验证成功: ${fileInfo.fileName}`); + validFiles.push(fileInfo); + } else if (fileInfo) { + console.log(`文件属于其他项目: ${fileInfo.projectId} != ${projectId}`); + invalidFileIds.push(fileId); + } else { + console.log(`文件不存在: ${fileId}`); + invalidFileIds.push(fileId); + } + } catch (error) { + console.error(`验证文件 ${fileId} 时出错:`, String(error)); + invalidFileIds.push(fileId); + } + } + + console.log(`文件验证完成: 有效${validFiles.length}个, 无效${invalidFileIds.length}个`); + + if (validFiles.length === 0) { + return NextResponse.json( + { + error: 'No valid files found', + debug: { + projectId, + requestedIds: fileIds, + invalidIds: invalidFileIds, + message: 'None of the requested files belong to this project or exist in the database' + } + }, + { status: 404 } + ); + } + + // 批量生成 GA 对 + console.log('开始批量生成GA对...'); + console.log('追加模式:', appendMode); + const results = await batchGenerateGaPairs( + projectId, + validFiles, + modelConfigId, + language, + appendMode // 传递追加模式参数 + ); + + // 统计结果 + const successCount = results.filter(r => r.success).length; + const failureCount = results.filter(r => !r.success).length; + + console.log(`批量生成完成: 成功${successCount}个, 失败${failureCount}个`); + + return NextResponse.json({ + success: true, + data: results, + summary: { + total: results.length, + success: successCount, + failure: failureCount, + processed: validFiles.length, + skipped: invalidFileIds.length + }, + message: `Generated GA pairs for ${successCount} files, ${failureCount} failed, ${invalidFileIds.length} files not found` + }); + } catch (error) { + console.error('Error batch generating GA pairs:', String(error)); + return NextResponse.json({ error: String(error) || 'Failed to batch generate GA pairs' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/current/route.js b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/current/route.js new file mode 100644 index 0000000..6570c50 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/current/route.js @@ -0,0 +1,161 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import LLMClient from '@/lib/llm/core/index'; +import { getModelConfigById } from '@/lib/db/model-config'; + +/** + * Get current question and generate answers from two models + */ +export async function GET(request, { params }) { + try { + const { projectId, taskId } = params; + + const task = await db.task.findFirst({ + where: { + id: taskId, + projectId, + taskType: 'blind-test' + } + }); + + if (!task) { + return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 }); + } + + if (task.status !== 0) { + return NextResponse.json({ code: 400, error: 'Task has ended' }, { status: 400 }); + } + + // Parse task detail + let detail = {}; + let modelInfo = {}; + try { + detail = task.detail ? JSON.parse(task.detail) : {}; + modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {}; + } catch (e) { + console.error('Failed to parse task detail:', e); + } + + const questionIds = detail.questionIds || detail.evalDatasetIds || []; + const currentIndex = detail.currentIndex || 0; + + // Check if all questions are completed + if (questionIds.length === 0 || currentIndex >= questionIds.length) { + return NextResponse.json({ + code: 0, + data: { + completed: true, + message: 'All questions completed' + } + }); + } + + // Fetch current question + const currentQuestionId = questionIds[currentIndex]; + const currentQuestion = await db.evalDatasets.findUnique({ + where: { id: currentQuestionId }, + select: { + id: true, + question: true, + questionType: true, + correctAnswer: true, + tags: true + } + }); + + if (!currentQuestion) { + return NextResponse.json({ code: 404, error: 'Question not found' }, { status: 404 }); + } + + // Fetch both model configs + const [modelConfigA, modelConfigB] = await Promise.all([ + getModelConfigById(modelInfo.modelA.providerId), + getModelConfigById(modelInfo.modelB.providerId) + ]); + + if (!modelConfigA || !modelConfigB) { + return NextResponse.json({ code: 400, error: 'Model configuration not found' }, { status: 400 }); + } + + // Build prompts + const systemPrompt = "You are a helpful assistant. Provide detailed and accurate answers to the user's question."; + const userPrompt = currentQuestion.question; + + // Call both models in parallel + const startTimeA = Date.now(); + const startTimeB = Date.now(); + + let answerA = ''; + let answerB = ''; + let errorA = null; + let errorB = null; + let durationA = 0; + let durationB = 0; + + try { + // Call model A + const clientA = new LLMClient(modelConfigA); + + const resultA = await clientA.chat([ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userPrompt } + ]); + + answerA = resultA.text || ''; + durationA = Date.now() - startTimeA; + } catch (err) { + console.error('Model A call failed:', err); + errorA = err.message; + durationA = Date.now() - startTimeA; + } + + try { + // Call model B + const clientB = new LLMClient(modelConfigB); + + const resultB = await clientB.chat([ + { role: 'system', content: systemPrompt }, + { role: 'user', content: userPrompt } + ]); + + answerB = resultB.text || ''; + durationB = Date.now() - startTimeB; + } catch (err) { + console.error('Model B call failed:', err); + errorB = err.message; + durationB = Date.now() - startTimeB; + } + + // Randomly swap positions (core blind-test behavior) + const isSwapped = Math.random() > 0.5; + + return NextResponse.json({ + code: 0, + data: { + completed: false, + currentIndex, + totalCount: evalDatasetIds.length, + question: currentQuestion, + // Blind test: do not reveal which model is which + leftAnswer: { + content: isSwapped ? answerB : answerA, + error: isSwapped ? errorB : errorA, + duration: isSwapped ? durationB : durationA + }, + rightAnswer: { + content: isSwapped ? answerA : answerB, + error: isSwapped ? errorA : errorB, + duration: isSwapped ? durationA : durationB + }, + // Server stores the actual mapping for scoring + _swap: isSwapped + } + }); + } catch (error) { + console.error('Failed to fetch current question:', error); + return NextResponse.json( + { code: 500, error: 'Failed to fetch current question', message: error.message }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/question/route.js b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/question/route.js new file mode 100644 index 0000000..07db8f1 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/question/route.js @@ -0,0 +1,64 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; + +/** + * Get current question info (including random swap info) + */ +export async function GET(request, { params }) { + const { projectId, taskId } = params; + + try { + if (!projectId || !taskId) { + return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 }); + } + + // Fetch task + const task = await db.task.findUnique({ + where: { id: taskId } + }); + + if (!task || task.taskType !== 'blind-test') { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + // Parse task detail + const detail = JSON.parse(task.detail || '{}'); + // Support both evalDatasetIds and questionIds + const questionIds = detail.questionIds || detail.evalDatasetIds || []; + const currentIndex = detail.currentIndex || 0; + + // Check if task is completed + if (questionIds.length === 0 || currentIndex >= questionIds.length) { + return NextResponse.json({ + completed: true, + currentIndex, + totalQuestions: questionIds.length + }); + } + + // Fetch current question + const currentQuestionId = questionIds[currentIndex]; + const currentQuestion = await db.evalDatasets.findUnique({ + where: { id: currentQuestionId } + }); + + if (!currentQuestion) { + return NextResponse.json({ error: 'Question not found' }, { status: 404 }); + } + + // Randomly decide whether to swap (core blind-test behavior) + const isSwapped = Math.random() > 0.5; + + return NextResponse.json({ + questionId: currentQuestion.id, + question: currentQuestion.question, + answer: currentQuestion.correctAnswer || '', + questionIndex: currentIndex + 1, + totalQuestions: questionIds.length, + isSwapped + }); + } catch (error) { + console.error('Failed to fetch question info:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/route.js new file mode 100644 index 0000000..473ed1b --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/route.js @@ -0,0 +1,190 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; + +/** + * Get blind-test task details + * Results are fetched from EvalResults table + */ +export async function GET(request, { params }) { + try { + const { projectId, taskId } = params; + + const task = await db.task.findFirst({ + where: { + id: taskId, + projectId, + taskType: 'blind-test' + } + }); + + if (!task) { + return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 }); + } + + let detail = {}; + let modelInfo = {}; + try { + detail = task.detail ? JSON.parse(task.detail) : {}; + modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {}; + } catch (e) { + console.error('Failed to parse task detail:', e); + } + + // Fetch all related evaluation questions + const evalDatasetIds = detail.evalDatasetIds || []; + const evalDatasets = await db.evalDatasets.findMany({ + where: { + id: { in: evalDatasetIds } + }, + select: { + id: true, + question: true, + questionType: true, + correctAnswer: true, + tags: true + } + }); + + // Sort by evalDatasetIds order + const orderedDatasets = evalDatasetIds.map(id => evalDatasets.find(d => d.id === id)).filter(Boolean); + + // Fetch results from EvalResults table + const evalResults = await db.evalResults.findMany({ + where: { taskId }, + orderBy: { createAt: 'asc' } + }); + + // Parse results into the format expected by frontend + const results = evalResults.map(r => { + let modelAnswer = {}; + let judgeData = {}; + try { + modelAnswer = JSON.parse(r.modelAnswer || '{}'); + judgeData = JSON.parse(r.judgeResponse || '{}'); + } catch (e) { + // Ignore parse errors + } + return { + questionId: r.evalDatasetId, + vote: judgeData.vote, + isSwapped: judgeData.isSwapped, + modelAScore: judgeData.modelAScore || 0, + modelBScore: judgeData.modelBScore || 0, + leftAnswer: modelAnswer.leftAnswer || '', + rightAnswer: modelAnswer.rightAnswer || '', + timestamp: r.createAt + }; + }); + + return NextResponse.json({ + code: 0, + data: { + ...task, + detail: { + ...detail, + results // Include results from EvalResults table + }, + modelInfo, + evalDatasets: orderedDatasets + } + }); + } catch (error) { + console.error('Failed to fetch blind-test task details:', error); + return NextResponse.json( + { code: 500, error: 'Failed to fetch blind-test task details', message: error.message }, + { status: 500 } + ); + } +} + +/** + * Update blind-test task (interrupt/stop) + */ +export async function PUT(request, { params }) { + try { + const { projectId, taskId } = params; + const { action } = await request.json(); + + const task = await db.task.findFirst({ + where: { + id: taskId, + projectId, + taskType: 'blind-test' + } + }); + + if (!task) { + return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 }); + } + + if (action === 'interrupt') { + if (task.status !== 0) { + return NextResponse.json({ code: 400, error: 'Only running tasks can be interrupted' }, { status: 400 }); + } + + const updatedTask = await db.task.update({ + where: { id: taskId }, + data: { + status: 3, // Interrupted + endTime: new Date() + } + }); + + return NextResponse.json({ + code: 0, + data: updatedTask, + message: 'Task interrupted' + }); + } + + return NextResponse.json({ code: 400, error: 'Unknown action' }, { status: 400 }); + } catch (error) { + console.error('Failed to update blind-test task:', error); + return NextResponse.json( + { code: 500, error: 'Failed to update blind-test task', message: error.message }, + { status: 500 } + ); + } +} + +/** + * Delete blind-test task and its results + */ +export async function DELETE(request, { params }) { + try { + const { projectId, taskId } = params; + + const task = await db.task.findFirst({ + where: { + id: taskId, + projectId, + taskType: 'blind-test' + } + }); + + if (!task) { + return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 }); + } + + // Delete related EvalResults first + await db.evalResults.deleteMany({ + where: { taskId } + }); + + // Then delete the task + await db.task.delete({ + where: { id: taskId } + }); + + return NextResponse.json({ + code: 0, + message: 'Task deleted' + }); + } catch (error) { + console.error('Failed to delete blind-test task:', error); + return NextResponse.json( + { code: 500, error: 'Failed to delete blind-test task', message: error.message }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream-model/route.js b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream-model/route.js new file mode 100644 index 0000000..beac0f1 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream-model/route.js @@ -0,0 +1,92 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import LLMClient from '@/lib/llm/core/index'; +import { getModelConfigById } from '@/lib/db/model-config'; + +/** + * Stream answer for a specified model + * Query param: model=A or model=B + */ +export async function GET(request, { params }) { + const { projectId, taskId } = params; + const { searchParams } = new URL(request.url); + const modelType = searchParams.get('model'); // 'A' or 'B' + + try { + if (!projectId || !taskId) { + return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 }); + } + + if (!modelType || !['A', 'B'].includes(modelType)) { + return NextResponse.json({ error: 'Model type must be specified (A or B)' }, { status: 400 }); + } + + // Fetch task + const task = await db.task.findUnique({ + where: { id: taskId } + }); + + if (!task || task.taskType !== 'blind-test') { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + // Parse task detail + const detail = JSON.parse(task.detail || '{}'); + const modelInfo = JSON.parse(task.modelInfo || '{}'); + // Support both evalDatasetIds and questionIds + const questionIds = detail.questionIds || detail.evalDatasetIds || []; + const currentIndex = detail.currentIndex || 0; + + // Check if task is completed + if (questionIds.length === 0 || currentIndex >= questionIds.length) { + return NextResponse.json({ completed: true }); + } + + // Fetch current question + const currentQuestionId = questionIds[currentIndex]; + const currentQuestion = await db.evalDatasets.findUnique({ + where: { id: currentQuestionId } + }); + + if (!currentQuestion) { + return NextResponse.json({ error: 'Question not found' }, { status: 404 }); + } + + // Resolve model config based on modelType + const modelConfigKey = modelType === 'A' ? 'modelA' : 'modelB'; + const modelConfig = await getModelConfigById(modelInfo[modelConfigKey].id); + + if (!modelConfig) { + return NextResponse.json({ error: 'Model configuration not found' }, { status: 400 }); + } + + // Prepare messages + const messages = [ + { + role: 'system', + content: "You are a helpful assistant. Provide detailed and accurate answers to the user's question." + }, + { role: 'user', content: currentQuestion.question } + ]; + + // Create LLM client + const client = new LLMClient({ + projectId, + ...modelConfig + }); + + // Call streaming API and return response directly + const response = await client.chatStreamAPI(messages); + + return new Response(response.body, { + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive' + } + }); + } catch (error) { + console.error(`Model ${modelType} streaming call failed:`, error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream/route.js b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream/route.js new file mode 100644 index 0000000..54f0002 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/stream/route.js @@ -0,0 +1,213 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import LLMClient from '@/lib/llm/core/index'; +import { getModelConfigById } from '@/lib/db/model-config'; + +/** + * Stream answers from two models for the current question + */ +export async function GET(request, { params }) { + const { projectId, taskId } = params; + + try { + if (!projectId || !taskId) { + return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 }); + } + + // Fetch task + const task = await db.task.findUnique({ + where: { id: taskId } + }); + + if (!task || task.taskType !== 'blind-test') { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + // Parse task detail + const detail = JSON.parse(task.detail || '{}'); + const modelInfo = JSON.parse(task.modelInfo || '{}'); + const { questionIds = [], currentIndex = 0 } = detail; + + // Check if task is completed + if (currentIndex >= questionIds.length) { + return NextResponse.json({ completed: true }); + } + + // Fetch current question + const currentQuestionId = questionIds[currentIndex]; + const currentQuestion = await db.evalDatasets.findUnique({ + where: { id: currentQuestionId } + }); + + if (!currentQuestion) { + return NextResponse.json({ error: 'Question not found' }, { status: 404 }); + } + + // Fetch model configs + const [modelConfigA, modelConfigB] = await Promise.all([ + getModelConfigById(modelInfo.modelA.providerId), + getModelConfigById(modelInfo.modelB.providerId) + ]); + + if (!modelConfigA || !modelConfigB) { + return NextResponse.json({ error: 'Model configuration not found' }, { status: 400 }); + } + + // Randomly swap positions (core blind-test behavior) + const isSwapped = Math.random() > 0.5; + + // Create streaming response + const encoder = new TextEncoder(); + const stream = new ReadableStream({ + async start(controller) { + try { + // Send init message + controller.enqueue( + encoder.encode( + JSON.stringify({ + type: 'init', + question: currentQuestion.question, + questionId: currentQuestion.id, + questionIndex: currentIndex + 1, + totalQuestions: questionIds.length, + isSwapped + }) + '\n' + ) + ); + + // Prepare messages + const messages = [ + { + role: 'system', + content: "You are a helpful assistant. Provide detailed and accurate answers to the user's question." + }, + { role: 'user', content: currentQuestion.question } + ]; + + // Create LLM clients + const clientA = new LLMClient({ + projectId, + ...modelConfigA + }); + + const clientB = new LLMClient({ + projectId, + ...modelConfigB + }); + + let answerA = ''; + let answerB = ''; + const startTime = Date.now(); + + // Call both models in parallel (streaming) + await Promise.all([ + (async () => { + try { + const response = await clientA.chatStreamAPI(messages); + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + answerA += chunk; + + // Send chunk update + controller.enqueue( + encoder.encode( + JSON.stringify({ + type: 'chunk', + model: isSwapped ? 'B' : 'A', + content: chunk + }) + '\n' + ) + ); + } + } catch (err) { + console.error('Model A call failed:', err); + controller.enqueue( + encoder.encode( + JSON.stringify({ + type: 'error', + model: isSwapped ? 'B' : 'A', + error: err.message + }) + '\n' + ) + ); + } + })(), + (async () => { + try { + const response = await clientB.chatStreamAPI(messages); + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + answerB += chunk; + + // Send chunk update + controller.enqueue( + encoder.encode( + JSON.stringify({ + type: 'chunk', + model: isSwapped ? 'A' : 'B', + content: chunk + }) + '\n' + ) + ); + } + } catch (err) { + console.error('Model B call failed:', err); + controller.enqueue( + encoder.encode( + JSON.stringify({ + type: 'error', + model: isSwapped ? 'A' : 'B', + error: err.message + }) + '\n' + ) + ); + } + })() + ]); + + const duration = Date.now() - startTime; + + // Send done message + controller.enqueue( + encoder.encode( + JSON.stringify({ + type: 'done', + duration, + answerA: isSwapped ? answerB : answerA, + answerB: isSwapped ? answerA : answerB + }) + '\n' + ) + ); + + controller.close(); + } catch (error) { + console.error('Streaming handler failed:', error); + controller.error(error); + } + } + }); + + return new Response(stream, { + headers: { + 'Content-Type': 'text/plain; charset=utf-8', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive' + } + }); + } catch (error) { + console.error('API error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/vote/route.js b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/vote/route.js new file mode 100644 index 0000000..1e3c264 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/[taskId]/vote/route.js @@ -0,0 +1,154 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; + +/** + * Submit vote result + * vote: 'left' | 'right' | 'both_good' | 'both_bad' + * Results are stored in EvalResults table + */ +export async function POST(request, { params }) { + try { + const { projectId, taskId } = params; + const { vote, questionId, isSwapped, leftAnswer, rightAnswer } = await request.json(); + + // Validate vote option + const validVotes = ['left', 'right', 'both_good', 'both_bad']; + if (!validVotes.includes(vote)) { + return NextResponse.json({ code: 400, error: 'Invalid vote option' }, { status: 400 }); + } + + if (!questionId) { + return NextResponse.json({ code: 400, error: 'Question ID is required' }, { status: 400 }); + } + + const task = await db.task.findFirst({ + where: { + id: taskId, + projectId, + taskType: 'blind-test' + } + }); + + if (!task) { + return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 }); + } + + if (task.status !== 0) { + return NextResponse.json({ code: 400, error: 'Task has ended' }, { status: 400 }); + } + + // Parse task details + let detail = {}; + try { + detail = task.detail ? JSON.parse(task.detail) : {}; + } catch (e) { + console.error('Failed to parse task detail:', e); + } + + // Calculate scores + // isSwapped: true means left is model B and right is model A + // isSwapped: false means left is model A and right is model B + let modelAScore = 0; + let modelBScore = 0; + + if (vote === 'left') { + if (isSwapped) { + modelBScore = 1; // Left is B + } else { + modelAScore = 1; // Left is A + } + } else if (vote === 'right') { + if (isSwapped) { + modelAScore = 1; // Right is A + } else { + modelBScore = 1; // Right is B + } + } else if (vote === 'both_good') { + modelAScore = 0.5; + modelBScore = 0.5; + } + // both_bad: both scores remain 0 + + // Store result in EvalResults table + const evalResult = await db.evalResults.create({ + data: { + projectId, + taskId, + evalDatasetId: questionId, + modelAnswer: JSON.stringify({ + leftAnswer: leftAnswer || '', + rightAnswer: rightAnswer || '' + }), + score: modelAScore, // Store modelA score for sorting/aggregation + isCorrect: false, // Not applicable for blind-test + judgeResponse: JSON.stringify({ + vote, + isSwapped, + modelAScore, + modelBScore + }), + duration: 0, + status: 0 + } + }); + + // Update task progress + const evalDatasetIds = detail.evalDatasetIds || []; + const newCurrentIndex = (detail.currentIndex || 0) + 1; + const isCompleted = newCurrentIndex >= evalDatasetIds.length; + + const updatedDetail = { + ...detail, + currentIndex: newCurrentIndex + }; + + await db.task.update({ + where: { id: taskId }, + data: { + detail: JSON.stringify(updatedDetail), + completedCount: newCurrentIndex, + status: isCompleted ? 1 : 0, // 1-completed, 0-running + endTime: isCompleted ? new Date() : null + } + }); + + // Calculate current total scores from EvalResults + const allResults = await db.evalResults.findMany({ + where: { taskId }, + select: { judgeResponse: true } + }); + + let totalModelAScore = 0; + let totalModelBScore = 0; + for (const r of allResults) { + try { + const judge = JSON.parse(r.judgeResponse || '{}'); + totalModelAScore += judge.modelAScore || 0; + totalModelBScore += judge.modelBScore || 0; + } catch (e) { + // Ignore parse errors + } + } + + return NextResponse.json({ + code: 0, + data: { + success: true, + isCompleted, + currentIndex: newCurrentIndex, + totalCount: evalDatasetIds.length, + scores: { + modelA: totalModelAScore, + modelB: totalModelBScore + } + }, + message: isCompleted ? 'Blind-test task completed' : 'Vote recorded' + }); + } catch (error) { + console.error('Failed to submit vote result:', error); + return NextResponse.json( + { code: 500, error: 'Failed to submit vote result', message: error.message }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/route.js b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/route.js new file mode 100644 index 0000000..41a8067 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/blind-test-tasks/route.js @@ -0,0 +1,226 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; + +/** + * Get all blind-test tasks for a project + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const page = parseInt(searchParams.get('page') || '1'); + const pageSize = parseInt(searchParams.get('pageSize') || '20'); + + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + const skip = (page - 1) * pageSize; + + // Fetch task list and total count + const [tasks, total] = await Promise.all([ + db.task.findMany({ + where: { + projectId, + taskType: 'blind-test' + }, + orderBy: { createAt: 'desc' }, + skip, + take: pageSize + }), + db.task.count({ + where: { + projectId, + taskType: 'blind-test' + } + }) + ]); + + // Fetch evaluation results for all tasks to calculate scores + const taskIds = tasks.map(t => t.id); + const allEvalResults = await db.evalResults.findMany({ + where: { taskId: { in: taskIds } }, + select: { + taskId: true, + judgeResponse: true + } + }); + + // Group results by taskId and calculate scores + const taskScores = {}; + for (const result of allEvalResults) { + if (!taskScores[result.taskId]) { + taskScores[result.taskId] = { modelAScore: 0, modelBScore: 0 }; + } + try { + const judge = JSON.parse(result.judgeResponse || '{}'); + taskScores[result.taskId].modelAScore += judge.modelAScore || 0; + taskScores[result.taskId].modelBScore += judge.modelBScore || 0; + } catch (e) { + // Ignore parse errors + } + } + + // Parse task detail fields and attach scores + const tasksWithDetails = tasks.map(task => { + let detail = {}; + let modelInfo = {}; + try { + detail = task.detail ? JSON.parse(task.detail) : {}; + modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {}; + } catch (e) { + console.error('Failed to parse task detail:', e); + } + + // Attach calculated scores as results array + const scores = taskScores[task.id] || { modelAScore: 0, modelBScore: 0 }; + const results = [ + { + modelAScore: scores.modelAScore, + modelBScore: scores.modelBScore + } + ]; + + return { + ...task, + detail: { + ...detail, + results // Attach results for display in task card + }, + modelInfo + }; + }); + + return NextResponse.json({ + code: 0, + data: { + items: tasksWithDetails, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize) + } + }); + } catch (error) { + console.error('Failed to fetch blind-test task list:', error); + return NextResponse.json( + { code: 500, error: 'Failed to fetch blind-test task list', message: error.message }, + { status: 500 } + ); + } +} + +/** + * Create a blind-test task + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const data = await request.json(); + + const { modelA, modelB, evalDatasetIds, language = 'zh-CN' } = data; + + if (!modelA || !modelA.modelId || !modelA.providerId) { + return NextResponse.json({ code: 400, error: 'Please select model A' }, { status: 400 }); + } + + if (!modelB || !modelB.modelId || !modelB.providerId) { + return NextResponse.json({ code: 400, error: 'Please select model B' }, { status: 400 }); + } + + if (modelA.modelId === modelB.modelId && modelA.providerId === modelB.providerId) { + return NextResponse.json({ code: 400, error: 'The two models must be different' }, { status: 400 }); + } + + if (!evalDatasetIds || evalDatasetIds.length === 0) { + return NextResponse.json({ code: 400, error: 'Please select questions to evaluate' }, { status: 400 }); + } + + const evalDatasets = await db.evalDatasets.findMany({ + where: { + id: { in: evalDatasetIds }, + projectId + }, + select: { id: true, questionType: true } + }); + + const invalidQuestions = evalDatasets.filter( + q => q.questionType !== 'short_answer' && q.questionType !== 'open_ended' + ); + + if (invalidQuestions.length > 0) { + return NextResponse.json( + { + code: 400, + error: 'Blind-test tasks only support short-answer and open-ended questions' + }, + { status: 400 } + ); + } + + // Fetch model config info + const [modelConfigA, modelConfigB] = await Promise.all([ + db.modelConfig.findFirst({ + where: { projectId, providerId: modelA.providerId, modelId: modelA.modelId } + }), + db.modelConfig.findFirst({ + where: { projectId, providerId: modelB.providerId, modelId: modelB.modelId } + }) + ]); + + // Build model info (two models) + const modelInfo = { + modelA: { + id: modelConfigA?.id, + modelId: modelA.modelId, + modelName: modelConfigA?.modelName || modelA.modelId, + providerId: modelA.providerId, + providerName: modelConfigA?.providerName || modelA.providerId + }, + modelB: { + id: modelConfigB?.id, + modelId: modelB.modelId, + modelName: modelConfigB?.modelName || modelB.modelId, + providerId: modelB.providerId, + providerName: modelConfigB?.providerName || modelB.providerId + } + }; + + // Build task detail (only store evalDatasetIds and currentIndex) + const taskDetail = { + evalDatasetIds, + currentIndex: 0 // Current question index + }; + + // Create task + const newTask = await db.task.create({ + data: { + projectId, + taskType: 'blind-test', + status: 0, // Running + modelInfo: JSON.stringify(modelInfo), + language, + detail: JSON.stringify(taskDetail), + totalCount: evalDatasetIds.length, + completedCount: 0, + note: '' + } + }); + + return NextResponse.json({ + code: 0, + data: { + ...newTask, + detail: taskDetail, + modelInfo + }, + message: 'Blind-test task created' + }); + } catch (error) { + console.error('Failed to create blind-test task:', error); + return NextResponse.json( + { code: 500, error: 'Failed to create blind-test task', message: error.message }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/clean/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/clean/route.js new file mode 100644 index 0000000..40872ff --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/clean/route.js @@ -0,0 +1,40 @@ +import { NextResponse } from 'next/server'; +import logger from '@/lib/util/logger'; +import cleanService from '@/lib/services/clean'; + +// 为指定文本块进行数据清洗 +export async function POST(request, { params }) { + try { + const { projectId, chunkId } = params; + + // 验证项目ID和文本块ID + if (!projectId || !chunkId) { + return NextResponse.json({ error: 'Project ID or text block ID cannot be empty' }, { status: 400 }); + } + + // 获取请求体 + const { model, language = '中文' } = await request.json(); + + if (!model) { + return NextResponse.json({ error: 'Model cannot be empty' }, { status: 400 }); + } + + // 使用数据清洗服务 + const result = await cleanService.cleanDataForChunk(projectId, chunkId, { + model, + language + }); + + // 返回清洗结果 + return NextResponse.json({ + chunkId, + originalLength: result.originalLength, + cleanedLength: result.cleanedLength, + success: result.success, + message: '数据清洗完成' + }); + } catch (error) { + logger.error('Error cleaning data:', error); + return NextResponse.json({ error: error.message || 'Error cleaning data' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/eval-questions/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/eval-questions/route.js new file mode 100644 index 0000000..5b35b6a --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/eval-questions/route.js @@ -0,0 +1,35 @@ +import { NextResponse } from 'next/server'; +import { generateEvalQuestionsForChunk } from '@/lib/services/eval'; +import logger from '@/lib/util/logger'; + +/** + * 为指定文本块生成测评题目 + */ +export async function POST(request, { params }) { + try { + const { projectId, chunkId } = params; + + // 验证参数 + if (!projectId || !chunkId) { + return NextResponse.json({ error: 'Project ID and Chunk ID are required' }, { status: 400 }); + } + + // 获取请求体 + const { model, language = 'zh-CN' } = await request.json(); + + if (!model) { + return NextResponse.json({ error: 'Model configuration is required' }, { status: 400 }); + } + + // 调用服务层生成测评题目 + const result = await generateEvalQuestionsForChunk(projectId, chunkId, { + model, + language + }); + + return NextResponse.json(result); + } catch (error) { + logger.error('Error generating eval questions:', error); + return NextResponse.json({ error: error.message || 'Failed to generate eval questions' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/questions/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/questions/route.js new file mode 100644 index 0000000..e502b5c --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/questions/route.js @@ -0,0 +1,73 @@ +import { NextResponse } from 'next/server'; +import { getQuestionsForChunk } from '@/lib/db/questions'; +import logger from '@/lib/util/logger'; +import questionService from '@/lib/services/questions'; + +// 为指定文本块生成问题 +export async function POST(request, { params }) { + try { + const { projectId, chunkId } = params; + + // 验证项目ID和文本块ID + if (!projectId || !chunkId) { + return NextResponse.json({ error: 'Project ID or text block ID cannot be empty' }, { status: 400 }); + } // 获取请求体 + const { model, language = '中文', number, enableGaExpansion = false } = await request.json(); + + if (!model) { + return NextResponse.json({ error: 'Model cannot be empty' }, { status: 400 }); + } + + // 后续会根据是否有GA对来选择是否启用GA扩展选择服务函数 + const serviceFunc = questionService.generateQuestionsForChunkWithGA; + + // 使用问题生成服务 + const result = await serviceFunc(projectId, chunkId, { + model, + language, + number, + enableGaExpansion + }); + + // 统一返回格式,确保包含GA扩展信息 + const response = { + chunkId, + questions: result.questions || result.labelQuestions || [], + total: result.total || (result.questions || result.labelQuestions || []).length, + gaExpansionUsed: result.gaExpansionUsed || false, + gaPairsCount: result.gaPairsCount || 0, + expectedTotal: result.expectedTotal || result.total + }; + + // 返回生成的问题 + return NextResponse.json(response); + } catch (error) { + logger.error('Error generating questions:', error); + return NextResponse.json({ error: error.message || 'Error generating questions' }, { status: 500 }); + } +} + +// 获取指定文本块的问题 +export async function GET(request, { params }) { + try { + const { projectId, chunkId } = params; + + // 验证项目ID和文本块ID + if (!projectId || !chunkId) { + return NextResponse.json({ error: 'The item ID or text block ID cannot be empty' }, { status: 400 }); + } + + // 获取文本块的问题 + const questions = await getQuestionsForChunk(projectId, chunkId); + + // 返回问题列表 + return NextResponse.json({ + chunkId, + questions, + total: questions.length + }); + } catch (error) { + console.error('Error getting questions:', String(error)); + return NextResponse.json({ error: error.message || 'Error getting questions' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/route.js new file mode 100644 index 0000000..4b91ee5 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/[chunkId]/route.js @@ -0,0 +1,73 @@ +import { NextResponse } from 'next/server'; +import { deleteChunkById, getChunkById, updateChunkById } from '@/lib/db/chunks'; + +// 获取文本块内容 +export async function GET(request, { params }) { + try { + const { projectId, chunkId } = params; + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + if (!chunkId) { + return NextResponse.json({ error: 'Text block ID cannot be empty' }, { status: 400 }); + } + // 获取文本块内容 + const chunk = await getChunkById(chunkId); + + return NextResponse.json(chunk); + } catch (error) { + console.error('Failed to get text block content:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to get text block content' }, { status: 500 }); + } +} + +// 删除文本块 +export async function DELETE(request, { params }) { + try { + const { projectId, chunkId } = params; + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + if (!chunkId) { + return NextResponse.json({ error: 'Text block ID cannot be empty' }, { status: 400 }); + } + await deleteChunkById(chunkId); + + return NextResponse.json({ message: 'Text block deleted successfully' }); + } catch (error) { + console.error('Failed to delete text block:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to delete text block' }, { status: 500 }); + } +} + +// 编辑文本块内容 +export async function PATCH(request, { params }) { + try { + const { projectId, chunkId } = params; + + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + if (!chunkId) { + return NextResponse.json({ error: '文本块ID不能为空' }, { status: 400 }); + } + + // 解析请求体获取新内容 + const requestData = await request.json(); + const { content } = requestData; + + if (!content) { + return NextResponse.json({ error: '内容不能为空' }, { status: 400 }); + } + + let res = await updateChunkById(chunkId, { content }); + return NextResponse.json(res); + } catch (error) { + console.error('编辑文本块失败:', String(error)); + return NextResponse.json({ error: error.message || '编辑文本块失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/batch-content/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/batch-content/route.js new file mode 100644 index 0000000..a754548 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/batch-content/route.js @@ -0,0 +1,20 @@ +import { getChunkContentsByNames } from '@/lib/db/chunks'; +import { NextResponse } from 'next/server'; + +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { chunkNames } = await request.json(); + + if (!chunkNames || !Array.isArray(chunkNames)) { + return NextResponse.json({ error: 'chunkNames 参数必须是数组' }, { status: 400 }); + } + + const chunkContentMap = await getChunkContentsByNames(projectId, chunkNames); + + return NextResponse.json(chunkContentMap); + } catch (error) { + console.error('批量获取文本块内容失败:', error); + return NextResponse.json({ error: '批量获取文本块内容失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/batch-edit/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/batch-edit/route.js new file mode 100644 index 0000000..4fb6514 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/batch-edit/route.js @@ -0,0 +1,102 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +/** + * 批量编辑文本块内容 + * POST /api/projects/[projectId]/chunks/batch-edit + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + const { position, content, chunkIds } = body; + + // 验证参数 + if (!position || !content || !chunkIds || !Array.isArray(chunkIds) || chunkIds.length === 0) { + return NextResponse.json({ error: 'Missing required parameters: position, content, chunkIds' }, { status: 400 }); + } + + if (!['start', 'end'].includes(position)) { + return NextResponse.json({ error: 'Position must be "start" or "end"' }, { status: 400 }); + } + + // 验证项目权限(获取要编辑的文本块) + const chunksToUpdate = await prisma.chunks.findMany({ + where: { + id: { in: chunkIds }, + projectId: projectId + }, + select: { + id: true, + content: true, + name: true + } + }); + + if (chunksToUpdate.length === 0) { + return NextResponse.json({ error: 'Not found' }, { status: 404 }); + } + + if (chunksToUpdate.length !== chunkIds.length) { + return NextResponse.json({ error: 'Some chunks not found' }, { status: 400 }); + } + + // 准备更新数据 + const updates = chunksToUpdate.map(chunk => { + let newContent; + + if (position === 'start') { + // 在开头添加内容 + newContent = content + '\n\n' + chunk.content; + } else { + // 在结尾添加内容 + newContent = chunk.content + '\n\n' + content; + } + + return { + where: { id: chunk.id }, + data: { + content: newContent, + size: newContent.length, + updateAt: new Date() + } + }; + }); + + async function processBatches(items, batchSize, processFn) { + const results = []; + for (let i = 0; i < items.length; i += batchSize) { + const batch = items.slice(i, i + batchSize); + const batchResults = await Promise.all(batch.map(processFn)); + results.push(...batchResults); + } + return results; + } + + const BATCH_SIZE = 50; // 每批处理 50 个 + await processBatches(updates, BATCH_SIZE, update => prisma.chunks.update(update)); + + // 记录操作日志(可选) + console.log(`Successfully updated ${chunksToUpdate.length} chunks`); + + return NextResponse.json({ + success: true, + updatedCount: chunksToUpdate.length, + message: `Successfully updated ${chunksToUpdate.length} chunks` + }); + } catch (error) { + console.error('批量编辑文本块失败:', error); + + return NextResponse.json( + { + error: 'Batch edit chunks failed', + details: error.message + }, + { status: 500 } + ); + } finally { + await prisma.$disconnect(); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/name/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/name/route.js new file mode 100644 index 0000000..5aa66d9 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/name/route.js @@ -0,0 +1,35 @@ +import { NextResponse } from 'next/server'; +import { getChunkByName } from '@/lib/db/chunks'; + +/** + * 根据文本块名称获取文本块 + * @param {Request} request 请求对象 + * @param {object} context 上下文,包含路径参数 + * @returns {Promise} 响应对象 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 从查询参数中获取 chunkName + const { searchParams } = new URL(request.url); + const chunkName = searchParams.get('chunkName'); + + if (!chunkName) { + return NextResponse.json({ error: '文本块名称不能为空' }, { status: 400 }); + } + + // 根据名称和项目ID查询文本块 + const chunk = await getChunkByName(projectId, chunkName); + + if (!chunk) { + return NextResponse.json({ error: '未找到指定的文本块' }, { status: 404 }); + } + + // 返回文本块信息 + return NextResponse.json(chunk); + } catch (error) { + console.error('根据名称获取文本块失败:', String(error)); + return NextResponse.json({ error: '获取文本块失败: ' + error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/chunks/route.js b/easy-dataset-main/app/api/projects/[projectId]/chunks/route.js new file mode 100644 index 0000000..d944448 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/chunks/route.js @@ -0,0 +1,21 @@ +import { NextResponse } from 'next/server'; +import { deleteChunkById, getChunkByFileIds, getChunkById, getChunksByFileIds, updateChunkById } from '@/lib/db/chunks'; + +// 获取文本块内容 +export async function POST(request, { params }) { + try { + const { projectId } = params; + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + const { array } = await request.json(); + // 获取文本块内容 + const chunk = await getChunksByFileIds(array); + + return NextResponse.json(chunk); + } catch (error) { + console.error('Failed to get text block content:', String(error)); + return NextResponse.json({ error: String(error) || 'Failed to get text block content' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/config/route.js b/easy-dataset-main/app/api/projects/[projectId]/config/route.js new file mode 100644 index 0000000..660d83d --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/config/route.js @@ -0,0 +1,36 @@ +import { NextResponse } from 'next/server'; +import { getProject, updateProject, getTaskConfig } from '@/lib/db/projects'; + +// 获取项目配置 +export async function GET(request, { params }) { + try { + const projectId = params.projectId; + const config = await getProject(projectId); + const taskConfig = await getTaskConfig(projectId); + return NextResponse.json({ ...config, ...taskConfig }); + } catch (error) { + console.error('获取项目配置失败:', String(error)); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +// 更新项目配置 +export async function PUT(request, { params }) { + try { + const projectId = params.projectId; + const newConfig = await request.json(); + const currentConfig = await getProject(projectId); + + // 只更新 prompts 部分 + const updatedConfig = { + ...currentConfig, + ...newConfig.prompts + }; + + const config = await updateProject(projectId, updatedConfig); + return NextResponse.json(config); + } catch (error) { + console.error('更新项目配置失败:', String(error)); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/custom-prompts/route.js b/easy-dataset-main/app/api/projects/[projectId]/custom-prompts/route.js new file mode 100644 index 0000000..64fc95a --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/custom-prompts/route.js @@ -0,0 +1,105 @@ +import { NextResponse } from 'next/server'; +import { + getCustomPrompts, + getCustomPrompt, + saveCustomPrompt, + deleteCustomPrompt, + batchSaveCustomPrompts, + toggleCustomPrompt, + getPromptTemplates +} from '@/lib/db/custom-prompts'; + +// 获取项目的自定义提示词 +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const promptType = searchParams.get('promptType'); + const language = searchParams.get('language'); + + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + const customPrompts = await getCustomPrompts(projectId, promptType, language); + const templates = await getPromptTemplates(); + + return NextResponse.json({ + success: true, + customPrompts, + templates + }); + } catch (error) { + console.error('获取自定义提示词失败:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +// 保存自定义提示词 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + // 批量保存 + if (body.prompts && Array.isArray(body.prompts)) { + const results = await batchSaveCustomPrompts(projectId, body.prompts); + return NextResponse.json({ + success: true, + results + }); + } + + // 单个保存 + const { promptType, promptKey, language, content } = body; + if (!promptType || !promptKey || !language || content === undefined) { + return NextResponse.json( + { + error: 'promptType, promptKey, language and content are required' + }, + { status: 400 } + ); + } + + const result = await saveCustomPrompt(projectId, promptType, promptKey, language, content); + return NextResponse.json({ + success: true, + result + }); + } catch (error) { + console.error('保存自定义提示词失败:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +// 删除自定义提示词 +export async function DELETE(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const promptType = searchParams.get('promptType'); + const promptKey = searchParams.get('promptKey'); + const language = searchParams.get('language'); + + if (!projectId || !promptType || !promptKey || !language) { + return NextResponse.json( + { + error: 'projectId, promptType, promptKey and language are required' + }, + { status: 400 } + ); + } + + const success = await deleteCustomPrompt(projectId, promptType, promptKey, language); + return NextResponse.json({ + success + }); + } catch (error) { + console.error('删除自定义提示词失败:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/custom-split/route.js b/easy-dataset-main/app/api/projects/[projectId]/custom-split/route.js new file mode 100644 index 0000000..d587407 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/custom-split/route.js @@ -0,0 +1,116 @@ +import { NextResponse } from 'next/server'; +import { saveChunks, deleteChunksByFileId } from '@/lib/db/chunks'; +import path from 'path'; +import fs from 'fs/promises'; +import { getProjectRoot } from '@/lib/db/base'; + +/** + * 处理自定义分块请求 + * @param {Request} request - 请求对象 + * @param {Object} params - 路由参数 + * @returns {Promise} - 响应对象 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { fileId, fileName, content, splitPoints } = await request.json(); + + // 参数验证 + if (!projectId || !fileId || !fileName || !content || !splitPoints) { + return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'Project does not exist' }, { status: 404 }); + } + + // 先删除该文件已有的文本块 + await deleteChunksByFileId(projectId, fileId); + + // 根据分块点将文件内容分割成多个块 + const customChunks = generateCustomChunks(projectId, fileId, fileName, content, splitPoints); + + // 保存新的文本块 + await saveChunks(customChunks); + + return NextResponse.json({ + success: true, + message: 'Custom chunks saved successfully', + totalChunks: customChunks.length + }); + } catch (error) { + console.error('自定义分块处理出错:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to process custom split request' }, { status: 500 }); + } +} + +/** + * 根据分块点生成自定义文本块 + * @param {string} projectId - 项目ID + * @param {string} fileId - 文件ID + * @param {string} fileName - 文件名 + * @param {string} content - 文件内容 + * @param {Array} splitPoints - 分块点数组 + * @returns {Array} - 生成的文本块数组 + */ +function generateCustomChunks(projectId, fileId, fileName, content, splitPoints) { + // 按位置排序分块点 + const sortedPoints = [...splitPoints].sort((a, b) => a.position - b.position); + + // 创建分块 + const chunks = []; + let startPos = 0; + + // 处理每个分块点 + for (let i = 0; i < sortedPoints.length; i++) { + const endPos = sortedPoints[i].position; + + // 提取当前分块内容 + const chunkContent = content.substring(startPos, endPos); + + // 跳过空白分块 + if (chunkContent.trim().length === 0) { + startPos = endPos; + continue; + } + + // 创建分块对象 + const chunk = { + projectId, + name: `${path.basename(fileName, path.extname(fileName))}-part-${i + 1}`, + fileId, + fileName, + content: chunkContent, + summary: `${fileName} 自定义分块 ${i + 1}/${sortedPoints.length + 1}`, + size: chunkContent.length + }; + + chunks.push(chunk); + startPos = endPos; + } + + // 添加最后一个分块(如果有内容) + const lastChunkContent = content.substring(startPos); + if (lastChunkContent.trim().length > 0) { + const lastChunk = { + projectId, + name: `${path.basename(fileName, path.extname(fileName))}-part-${sortedPoints.length + 1}`, + fileId, + fileName, + content: lastChunkContent, + summary: `${fileName} 自定义分块 ${sortedPoints.length + 1}/${sortedPoints.length + 1}`, + size: lastChunkContent.length + }; + + chunks.push(lastChunk); + } + + return chunks; +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/[conversationId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/[conversationId]/route.js new file mode 100644 index 0000000..065b9d0 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/[conversationId]/route.js @@ -0,0 +1,183 @@ +/** + * 单个多轮对话数据集操作API + */ + +import { NextResponse } from 'next/server'; +import { + getDatasetConversationById, + updateDatasetConversation, + deleteDatasetConversation, + getConversationNavigationItems +} from '@/lib/db/dataset-conversations'; + +/** + * 获取单个多轮对话数据集详情 + */ +export async function GET(request, { params }) { + try { + const { projectId, conversationId } = params; + const { searchParams } = new URL(request.url); + const operateType = searchParams.get('operateType'); + + // 如果是导航操作,返回导航项 + if (operateType !== null) { + const data = await getConversationNavigationItems(projectId, conversationId, operateType); + return NextResponse.json(data); + } + + const conversation = await getDatasetConversationById(conversationId); + + if (!conversation) { + return NextResponse.json( + { + success: false, + message: '对话数据集不存在' + }, + { status: 404 } + ); + } + + if (conversation.projectId !== projectId) { + return NextResponse.json( + { + success: false, + message: '对话数据集不属于指定项目' + }, + { status: 403 } + ); + } + + return NextResponse.json(conversation); + } catch (error) { + console.error('获取多轮对话数据集详情失败:', error); + return NextResponse.json( + { + success: false, + message: error.message + }, + { status: 500 } + ); + } +} + +/** + * 更新多轮对话数据集 + */ +export async function PUT(request, { params }) { + try { + const { projectId, conversationId } = params; + const body = await request.json(); + + // 验证对话数据集是否存在且属于项目 + const conversation = await getDatasetConversationById(conversationId); + + if (!conversation) { + return NextResponse.json( + { + success: false, + message: '对话数据集不存在' + }, + { status: 404 } + ); + } + + if (conversation.projectId !== projectId) { + return NextResponse.json( + { + success: false, + message: '对话数据集不属于指定项目' + }, + { status: 403 } + ); + } + + // 只允许更新特定字段 + const allowedFields = ['score', 'tags', 'note', 'confirmed', 'aiEvaluation', 'messages']; + const updateData = {}; + + allowedFields.forEach(field => { + if (body.hasOwnProperty(field)) { + if (field === 'messages') { + // 将messages数组转换为rawMessages字符串存储 + updateData['rawMessages'] = JSON.stringify(body[field]); + } else { + updateData[field] = body[field]; + } + } + }); + + if (Object.keys(updateData).length === 0) { + return NextResponse.json( + { + success: false, + message: '没有有效的更新字段' + }, + { status: 400 } + ); + } + + const updatedConversation = await updateDatasetConversation(conversationId, updateData); + + return NextResponse.json({ + success: true, + data: updatedConversation + }); + } catch (error) { + console.error('更新多轮对话数据集失败:', error); + return NextResponse.json( + { + success: false, + message: error.message + }, + { status: 500 } + ); + } +} + +/** + * 删除多轮对话数据集 + */ +export async function DELETE(request, { params }) { + try { + const { projectId, conversationId } = params; + + // 验证对话数据集是否存在且属于项目 + const conversation = await getDatasetConversationById(conversationId); + + if (!conversation) { + return NextResponse.json( + { + success: false, + message: '对话数据集不存在' + }, + { status: 404 } + ); + } + + if (conversation.projectId !== projectId) { + return NextResponse.json( + { + success: false, + message: '对话数据集不属于指定项目' + }, + { status: 403 } + ); + } + + await deleteDatasetConversation(conversationId); + + return NextResponse.json({ + success: true, + message: '删除成功' + }); + } catch (error) { + console.error('删除多轮对话数据集失败:', error); + return NextResponse.json( + { + success: false, + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/export/route.js b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/export/route.js new file mode 100644 index 0000000..ba51d2a --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/export/route.js @@ -0,0 +1,68 @@ +/** + * 多轮对话数据集导出API + * 直接导出原始的 ShareGPT 格式数据集 + */ + +import { NextResponse } from 'next/server'; +import { getAllDatasetConversations } from '@/lib/db/dataset-conversations'; + +/** + * 导出多轮对话数据集 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + // 筛选条件 + const filters = { + confirmed: searchParams.get('confirmed') + }; + + // 清除空值 + Object.keys(filters).forEach(key => { + if (!filters[key]) delete filters[key]; + }); + + // 获取所有对话数据集 + const conversations = await getAllDatasetConversations(projectId, filters); + + if (conversations.length === 0) { + return NextResponse.json([]); + } + + // 转换为 ShareGPT 格式数组 + const shareGptData = []; + + for (const conversation of conversations) { + try { + // 解析 rawMessages + const messages = JSON.parse(conversation.rawMessages || '[]'); + + if (messages.length > 0) { + // 构建 ShareGPT 格式对象 + const shareGptItem = { + messages: messages + }; + + shareGptData.push(shareGptItem); + } + } catch (error) { + console.error(`解析对话消息失败 ${conversation.id}:`, error); + // 跳过解析失败的对话,继续处理其他对话 + continue; + } + } + + return NextResponse.json(shareGptData); + } catch (error) { + console.error('导出多轮对话数据集失败:', error); + return NextResponse.json( + { + success: false, + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/route.js b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/route.js new file mode 100644 index 0000000..76aa359 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/route.js @@ -0,0 +1,135 @@ +/** + * 多轮对话数据集管理API + */ + +import { NextResponse } from 'next/server'; +import { + getDatasetConversationsByPagination, + getAllDatasetConversationIds, + createDatasetConversation +} from '@/lib/db/dataset-conversations'; +import { generateMultiTurnConversation } from '@/lib/services/multi-turn/index'; + +/** + * 获取多轮对话数据集列表(支持分页和筛选) + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + const getAllIds = searchParams.get('getAllIds') === 'true'; // 新增:获取所有对话ID的标志 + + // 筛选条件 + const filters = { + keyword: searchParams.get('keyword'), + roleA: searchParams.get('roleA'), + roleB: searchParams.get('roleB'), + scenario: searchParams.get('scenario'), + scoreMin: searchParams.get('scoreMin'), + scoreMax: searchParams.get('scoreMax'), + confirmed: searchParams.get('confirmed') + }; + + // 清除空值 + Object.keys(filters).forEach(key => { + if (!filters[key]) delete filters[key]; + }); + + // 如果请求获取所有ID + if (getAllIds) { + const allConversationIds = await getAllDatasetConversationIds(projectId, filters); + return NextResponse.json({ allConversationIds }); + } + + // 正常分页查询 + const page = parseInt(searchParams.get('page') || '1'); + const pageSize = parseInt(searchParams.get('pageSize') || '20'); + + const result = await getDatasetConversationsByPagination(projectId, page, pageSize, filters); + + return NextResponse.json({ + success: true, + ...result + }); + } catch (error) { + console.error('获取多轮对话数据集失败:', error); + return NextResponse.json( + { + success: false, + message: error.message + }, + { status: 500 } + ); + } +} + +/** + * 创建多轮对话数据集 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + const { questionId, systemPrompt, scenario, rounds, roleA, roleB, model, language = '中文' } = body; + + if (!questionId) { + return NextResponse.json( + { + success: false, + message: '问题ID不能为空' + }, + { status: 400 } + ); + } + + if (!model || !model.modelId) { + return NextResponse.json( + { + success: false, + message: '模型配置不能为空' + }, + { status: 400 } + ); + } + + // 构建配置 + const config = { + systemPrompt: systemPrompt || '', + scenario: scenario || '', + rounds: rounds || 3, + roleA: roleA || '用户', + roleB: roleB || '助手', + model, + language + }; + + // 生成多轮对话 + const result = await generateMultiTurnConversation(projectId, questionId, config); + + if (!result.success) { + return NextResponse.json( + { + success: false, + message: result.error + }, + { status: 500 } + ); + } + + return NextResponse.json({ + success: true, + data: result.data + }); + } catch (error) { + console.error('创建多轮对话数据集失败:', error); + return NextResponse.json( + { + success: false, + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/tags/route.js b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/tags/route.js new file mode 100644 index 0000000..080e6cc --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/dataset-conversations/tags/route.js @@ -0,0 +1,42 @@ +import { NextResponse } from 'next/server'; +import { getAllDatasetConversations } from '@/lib/db/dataset-conversations'; + +/** + * 获取项目中多轮对话数据集的所有标签 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + // 获取项目所有对话数据集 + const conversations = await getAllDatasetConversations(projectId); + + // 提取所有标签 + const allTags = new Set(); + + conversations.forEach(conversation => { + if (conversation.tags && typeof conversation.tags === 'string') { + const tags = conversation.tags.split(/\s+/).filter(tag => tag.trim().length > 0); + tags.forEach(tag => allTags.add(tag.trim())); + } + }); + + return NextResponse.json({ + success: true, + tags: Array.from(allTags).sort() + }); + } catch (error) { + console.error('获取对话标签失败:', error); + return NextResponse.json( + { + success: false, + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/copy-to-eval/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/copy-to-eval/route.js new file mode 100644 index 0000000..31a8075 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/copy-to-eval/route.js @@ -0,0 +1,77 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; + +export async function POST(req, { params }) { + try { + const { projectId, datasetId } = params; + + // 1. 获取数据集详情 + const dataset = await db.datasets.findUnique({ + where: { id: datasetId, projectId } + }); + + if (!dataset) { + return NextResponse.json({ error: 'Dataset not found' }, { status: 404 }); + } + + // 2. 尝试通过 questionId 查找关联的 chunkId + let chunkId = null; + if (dataset.questionId) { + const question = await db.questions.findUnique({ + where: { id: dataset.questionId } + }); + if (question) { + chunkId = question.chunkId; + } + } + + // 3. 创建评估数据集记录 + // 默认使用 open_ended 类型,因为通常数据集是问答对,适合作为评估 + let evalTags = []; + try { + evalTags = JSON.parse(dataset.tags || '[]'); + if (!Array.isArray(evalTags)) evalTags = []; + } catch (e) { + evalTags = []; + } + + // 排除 'Eval' 标签,并将数组转为逗号分隔的字符串 + const evalTagsString = evalTags.filter(tag => tag !== 'Eval').join(','); + + const evalDataset = await db.evalDatasets.create({ + data: { + projectId, + question: dataset.question, + questionType: 'open_ended', + correctAnswer: dataset.answer, + tags: evalTagsString, + note: dataset.note, + chunkId: chunkId, + options: '' // 开放题不需要选项 + } + }); + + // 4. 更新原数据集,添加 'Eval' 标签 + let currentTags = []; + try { + currentTags = JSON.parse(dataset.tags || '[]'); + } catch (e) { + // ignore error + } + + if (!currentTags.includes('Eval')) { + currentTags.push('Eval'); + await db.datasets.update({ + where: { id: datasetId }, + data: { + tags: JSON.stringify(currentTags) + } + }); + } + + return NextResponse.json({ success: true, evalDataset }); + } catch (error) { + console.error('Failed to copy dataset to eval:', error); + return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/evaluate/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/evaluate/route.js new file mode 100644 index 0000000..ff8d455 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/evaluate/route.js @@ -0,0 +1,36 @@ +import { NextResponse } from 'next/server'; +import { evaluateDataset } from '@/lib/services/datasets/evaluation'; + +/** + * 评估单个数据集的质量 + */ +export async function POST(request, { params }) { + try { + const { projectId, datasetId } = params; + const { model, language = 'zh-CN' } = await request.json(); + + if (!projectId || !datasetId) { + return NextResponse.json({ success: false, message: '项目ID和数据集ID不能为空' }, { status: 400 }); + } + + if (!model) { + return NextResponse.json({ success: false, message: '模型配置不能为空' }, { status: 400 }); + } + + // 使用评估服务进行数据集评估 + const result = await evaluateDataset(projectId, datasetId, model, language); + + if (!result.success) { + return NextResponse.json({ success: false, message: result.error }, { status: 500 }); + } + + return NextResponse.json({ + success: true, + message: '数据集评估完成', + data: result.data + }); + } catch (error) { + console.error('数据集评估失败:', error); + return NextResponse.json({ success: false, message: `评估失败: ${error.message}` }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/route.js new file mode 100644 index 0000000..4964c9e --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/route.js @@ -0,0 +1,82 @@ +import { NextResponse } from 'next/server'; +import { getDatasetsById, getDatasetsCounts, getNavigationItems, updateDatasetMetadata } from '@/lib/db/datasets'; + +/** + * 获取项目的所有数据集 + */ +export async function GET(request, { params }) { + try { + const { projectId, datasetId } = params; + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + if (!datasetId) { + return NextResponse.json({ error: '数据集ID不能为空' }, { status: 400 }); + } + const { searchParams } = new URL(request.url); + const operateType = searchParams.get('operateType'); + if (operateType !== null) { + const data = await getNavigationItems(projectId, datasetId, operateType); + return NextResponse.json(data); + } + const datasets = await getDatasetsById(datasetId); + let counts = await getDatasetsCounts(projectId); + + return NextResponse.json({ datasets, ...counts }); + } catch (error) { + console.error('获取数据集详情失败:', String(error)); + return NextResponse.json( + { + error: error.message || '获取数据集详情失败' + }, + { status: 500 } + ); + } +} + +/** + * 更新数据集元数据(评分、标签、备注) + */ +export async function PATCH(request, { params }) { + try { + const { projectId, datasetId } = params; + + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + if (!datasetId) { + return NextResponse.json({ error: '数据集ID不能为空' }, { status: 400 }); + } + + const body = await request.json(); + const { score, tags, note } = body; + + // 验证评分范围 + if (score !== undefined && (score < 0 || score > 5)) { + return NextResponse.json({ error: '评分必须在0-5之间' }, { status: 400 }); + } + + // 验证标签格式 + if (tags !== undefined && !Array.isArray(tags)) { + return NextResponse.json({ error: '标签必须是数组格式' }, { status: 400 }); + } + + // 更新数据集元数据 + const updatedDataset = await updateDatasetMetadata(datasetId, { score, tags, note }); + + return NextResponse.json({ + success: true, + dataset: updatedDataset + }); + } catch (error) { + console.error('更新数据集元数据失败:', String(error)); + return NextResponse.json( + { + error: error.message || '更新数据集元数据失败' + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/token-count/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/token-count/route.js new file mode 100644 index 0000000..37ba645 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/[datasetId]/token-count/route.js @@ -0,0 +1,52 @@ +import { NextResponse } from 'next/server'; +import { getDatasetsById } from '@/lib/db/datasets'; +import { getEncoding } from '@langchain/core/utils/tiktoken'; + +/** + * 异步计算数据集文本的Token数量 + */ +export async function GET(request, { params }) { + try { + const { projectId, datasetId } = params; + + if (!datasetId) { + return NextResponse.json({ error: '数据集ID不能为空' }, { status: 400 }); + } + + const datasets = await getDatasetsById(datasetId); + const tokenCounts = { + answerTokens: 0, + cotTokens: 0 + }; + + try { + if (datasets.answer || datasets.cot) { + // 使用 cl100k_base 编码,适用于 gpt-3.5-turbo 和 gpt-4 + const encoding = await getEncoding('cl100k_base'); + + if (datasets.answer) { + const tokens = encoding.encode(datasets.answer); + tokenCounts.answerTokens = tokens.length; + } + + if (datasets.cot) { + const tokens = encoding.encode(datasets.cot); + tokenCounts.cotTokens = tokens.length; + } + } + } catch (error) { + console.error('计算Token数量失败:', String(error)); + return NextResponse.json({ error: '计算Token数量失败' }, { status: 500 }); + } + + return NextResponse.json(tokenCounts); + } catch (error) { + console.error('获取Token计数失败:', String(error)); + return NextResponse.json( + { + error: error.message || '获取Token计数失败' + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/batch-evaluate/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/batch-evaluate/route.js new file mode 100644 index 0000000..5803580 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/batch-evaluate/route.js @@ -0,0 +1,55 @@ +/** + * 批量数据集评估任务API + * 创建批量评估数据集质量的异步任务 + */ + +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import { processTask } from '@/lib/services/tasks/index'; + +/** + * 创建批量数据集评估任务 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { model, language = 'zh-CN' } = await request.json(); + + if (!projectId) { + return NextResponse.json({ success: false, message: '项目ID不能为空' }, { status: 400 }); + } + + if (!model || !model.modelId) { + return NextResponse.json({ success: false, message: '模型配置不能为空' }, { status: 400 }); + } + + // 创建批量评估任务 + const newTask = await db.task.create({ + data: { + projectId, + taskType: 'dataset-evaluation', + status: 0, // 初始状态: 处理中 + modelInfo: JSON.stringify(model), + language: language || 'zh-CN', + detail: '', + totalCount: 0, + note: '准备开始批量评估数据集质量...', + completedCount: 0 + } + }); + + // 异步处理任务 + processTask(newTask.id).catch(err => { + console.error(`批量评估任务启动失败: ${newTask.id}`, String(err)); + }); + + return NextResponse.json({ + success: true, + message: '批量评估任务已创建', + data: { taskId: newTask.id } + }); + } catch (error) { + console.error('创建批量评估任务失败:', error); + return NextResponse.json({ success: false, message: `创建任务失败: ${error.message}` }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/export/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/export/route.js new file mode 100644 index 0000000..f7982f6 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/export/route.js @@ -0,0 +1,128 @@ +import { NextResponse } from 'next/server'; +import { + getDatasets, + getBalancedDatasetsByTags, + getTagsWithDatasetCounts, + getDatasetsBatch, + getBalancedDatasetsByTagsBatch, + getDatasetsByIds, + getDatasetsByIdsBatch +} from '@/lib/db/datasets'; + +/** + * 获取导出数据集 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + + const confirmedParam = searchParams.get('confirmed'); + const confirmed = confirmedParam === null ? undefined : confirmedParam === 'true'; + + // 获取标签统计信息 + const tagStats = await getTagsWithDatasetCounts(projectId, confirmed); + return NextResponse.json(tagStats); + } catch (error) { + console.error('Failed to get tag statistics:', String(error)); + return NextResponse.json( + { + error: error.message || 'Failed to get tag statistics' + }, + { status: 500 } + ); + } +} + +/** + * 获取标签统计信息 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + + let status = body.status; + let confirmed = undefined; + if (status === 'confirmed') confirmed = true; + if (status === 'unconfirmed') confirmed = false; + + // 检查是否是分批导出模式 + const batchMode = body.batchMode ? 'true' : 'false'; + const offset = body.offset ?? 0; + const batchSize = body.batchSize ?? 1000; + + // 检查是否是平衡导出 + const balanceMode = body.balanceMode ? 'true' : 'false'; + const balanceConfig = body.balanceConfig; + + // 检查是否有选中的数据集 ID + const selectedIds = Array.isArray(body.selectedIds) ? body.selectedIds : null; + + if (batchMode === 'true') { + // 分批导出模式 + if (selectedIds && selectedIds.length > 0) { + // 按选中 ID 分批导出 + const datasets = await getDatasetsByIdsBatch(projectId, selectedIds, offset, batchSize); + const hasMore = datasets.length === batchSize; + return NextResponse.json({ + data: datasets, + hasMore, + offset: offset + datasets.length + }); + } else if (balanceMode === 'true' && balanceConfig) { + // 平衡分批导出 + const parsedConfig = typeof balanceConfig === 'string' ? JSON.parse(balanceConfig) : balanceConfig; + const result = await getBalancedDatasetsByTagsBatch(projectId, parsedConfig, confirmed, offset, batchSize); + return NextResponse.json({ + data: result.data, + hasMore: result.hasMore, + offset: offset + result.data.length + }); + } else { + // 常规分批导出 + const datasets = await getDatasetsBatch(projectId, confirmed, offset, batchSize); + const hasMore = datasets.length === batchSize; + return NextResponse.json({ + data: datasets, + hasMore, + offset: offset + datasets.length + }); + } + } else { + // 传统一次性导出模式(保持向后兼容) + if (selectedIds && selectedIds.length > 0) { + // 按选中 ID 导出 + const datasets = await getDatasetsByIds(projectId, selectedIds); + return NextResponse.json(datasets); + } else if (balanceMode === 'true' && balanceConfig) { + // 平衡导出模式 + const parsedConfig = typeof balanceConfig === 'string' ? JSON.parse(balanceConfig) : balanceConfig; + const datasets = await getBalancedDatasetsByTags(projectId, parsedConfig, confirmed); + return NextResponse.json(datasets); + } else { + // 常规导出模式 + const datasets = await getDatasets(projectId, confirmed); + return NextResponse.json(datasets); + } + } + } catch (error) { + console.error('Failed to get datasets:', String(error)); + return NextResponse.json( + { + error: error.message || 'Failed to get datasets' + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/generate-eval-variant/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/generate-eval-variant/route.js new file mode 100644 index 0000000..c6458a8 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/generate-eval-variant/route.js @@ -0,0 +1,44 @@ +import { NextResponse } from 'next/server'; +import { getDatasetsById } from '@/lib/db/datasets'; +import LLMClient from '@/lib/llm/core/index'; +import { getEvalQuestionPrompt } from '@/lib/llm/prompts/evalQuestion'; +import { extractJsonFromLLMOutput } from '@/lib/llm/common/util'; + +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { datasetId, model, language, questionType = 'open_ended', count = 1 } = await request.json(); + + if (!datasetId || !model) { + return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 }); + } + + // 1. 获取原数据集 + const dataset = await getDatasetsById(datasetId); + if (!dataset) { + return NextResponse.json({ error: 'Dataset not found' }, { status: 404 }); + } + + // 2. 构建提示词 + // 将原问题和答案合并作为上下文文本 + const text = `Question: ${dataset.question}\nAnswer: ${dataset.answer}`; + + const prompt = await getEvalQuestionPrompt(language || 'zh-CN', questionType, { text, number: count }, projectId); + + // 3. 调用 LLM + const client = new LLMClient(model); + + const response = await client.getResponse(prompt); + const result = extractJsonFromLLMOutput(response); + + // 结果应该是一个数组 + if (!result || !Array.isArray(result)) { + throw new Error('Failed to parse LLM output or output is not an array'); + } + + return NextResponse.json({ success: true, data: result }); + } catch (error) { + console.error('Generate eval variant failed:', error); + return NextResponse.json({ error: error.message || 'Internal Server Error' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/import/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/import/route.js new file mode 100644 index 0000000..86de7ef --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/import/route.js @@ -0,0 +1,109 @@ +import { NextResponse } from 'next/server'; +import { createDataset } from '@/lib/db/datasets'; +import { nanoid } from 'nanoid'; + +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { datasets, sourceInfo } = await request.json(); + + if (!datasets || !Array.isArray(datasets)) { + return NextResponse.json({ error: 'Invalid datasets data' }, { status: 400 }); + } + + const results = []; + const errors = []; + let successCount = 0; + let skippedCount = 0; + + for (let i = 0; i < datasets.length; i++) { + try { + const dataset = datasets[i]; + + // 安全获取与清洗字段 + const q = typeof dataset?.question === 'string' ? dataset.question.trim() : ''; + const a = typeof dataset?.answer === 'string' ? dataset.answer.trim() : ''; + + // 验证必填字段:缺失则跳过 + if (!q || !a) { + errors.push(`第 ${i + 1} 条记录缺少必填字段(question/answer),已跳过`); + skippedCount++; + continue; + } + + // 规范化可选字段 + const chunkName = dataset?.chunkName || 'Imported Data'; + const chunkContent = dataset?.chunkContent || 'Imported from external source'; + const model = dataset?.model || 'imported'; + const questionLabel = dataset?.questionLabel || ''; + const cot = typeof dataset?.cot === 'string' ? dataset.cot : ''; + const confirmed = typeof dataset?.confirmed === 'boolean' ? dataset.confirmed : false; + const score = typeof dataset?.score === 'number' ? dataset.score : 0; + // tags: 支持数组/字符串/对象 + let tags = '[]'; + if (Array.isArray(dataset?.tags)) { + try { + tags = JSON.stringify(dataset.tags); + } catch { + tags = '[]'; + } + } else if (typeof dataset?.tags === 'string') { + tags = dataset.tags; + } else if (dataset?.tags && typeof dataset.tags === 'object') { + try { + tags = JSON.stringify(dataset.tags); + } catch { + tags = '[]'; + } + } + // other: 对象或字符串 + let other = '{}'; + if (typeof dataset?.other === 'string') { + other = dataset.other; + } else if (dataset?.other && typeof dataset.other === 'object') { + try { + other = JSON.stringify(dataset.other); + } catch { + other = '{}'; + } + } + const note = typeof dataset?.note === 'string' ? dataset.note : ''; + + // 创建数据集记录 + const newDataset = await createDataset({ + projectId, + questionId: nanoid(), // 生成唯一的问题ID + question: q, + answer: a, + chunkName, + chunkContent, + model, + questionLabel, + cot, + confirmed, + score, + tags, + note, + other + }); + + results.push(newDataset); + successCount++; + } catch (error) { + errors.push(`第 ${i + 1} 条记录: ${error.message}`); + } + } + + return NextResponse.json({ + success: successCount, + total: datasets.length, + failed: errors.length, + skipped: skippedCount, + errors, + sourceInfo + }); + } catch (error) { + console.error('Import datasets error:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/optimize/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/optimize/route.js new file mode 100644 index 0000000..7a0067c --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/optimize/route.js @@ -0,0 +1,89 @@ +import { NextResponse } from 'next/server'; +import { getDatasetsById, updateDataset } from '@/lib/db/datasets'; +import { getQuestionById } from '@/lib/db/questions'; +import { getChunkById } from '@/lib/db/chunks'; +import LLMClient from '@/lib/llm/core/index'; +import { getNewAnswerPrompt } from '@/lib/llm/prompts/newAnswer'; +import { extractJsonFromLLMOutput } from '@/lib/llm/common/util'; + +// 优化数据集答案 +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + + // 获取请求体 + const { datasetId, model, advice, language } = await request.json(); + + if (!datasetId) { + return NextResponse.json({ error: 'Dataset ID cannot be empty' }, { status: 400 }); + } + + if (!model) { + return NextResponse.json({ error: 'Model cannot be empty' }, { status: 400 }); + } + + if (!advice) { + return NextResponse.json({ error: 'Please provide optimization suggestions' }, { status: 400 }); + } + + // 获取数据集内容 + const dataset = await getDatasetsById(datasetId); + if (!dataset) { + return NextResponse.json({ error: 'Dataset does not exist' }, { status: 404 }); + } + + // 创建LLM客户端 + const llmClient = new LLMClient(model); + + const { question, answer, cot, chunkContent: storedChunkContent, questionId } = dataset; + + let chunkContent = storedChunkContent || ''; + + if (!chunkContent && questionId) { + try { + const questionRecord = await getQuestionById(questionId); + if (questionRecord?.chunkId) { + const chunkRecord = await getChunkById(questionRecord.chunkId); + chunkContent = chunkRecord?.content || ''; + } + } catch (error) { + console.error('Failed to load chunk content by questionId:', error); + } + } + + // 生成优化后的答案和思维链 + const prompt = await getNewAnswerPrompt(language, { question, answer, cot, advice, chunkContent }, projectId); + + const response = await llmClient.getResponse(prompt); + + // 从LLM输出中提取JSON格式的优化结果 + const optimizedResult = extractJsonFromLLMOutput(response); + + if (!optimizedResult || !optimizedResult.answer) { + return NextResponse.json({ error: 'Failed to optimize answer, please try again' }, { status: 500 }); + } + + // 更新数据集 + const updatedDataset = { + ...dataset, + answer: optimizedResult.answer, + cot: cot ? optimizedResult.cot || cot : '' // 如果没有提供思考过程,则不更新 + }; + + await updateDataset(updatedDataset); + + // 返回优化后的数据集 + return NextResponse.json({ + success: true, + dataset: updatedDataset + }); + } catch (error) { + console.error('Failed to optimize answer:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to optimize answer' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/route.js new file mode 100644 index 0000000..88e5e17 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/route.js @@ -0,0 +1,193 @@ +import { NextResponse } from 'next/server'; +import { + deleteDataset, + getDatasetsByPagination, + getDatasetsIds, + getDatasetsById, + updateDataset +} from '@/lib/db/datasets'; +import datasetService from '@/lib/services/datasets'; + +// 优化思维链函数已移至服务层 + +/** + * 生成数据集(为单个问题生成答案) + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { questionId, model, language } = await request.json(); + + // 使用数据集生成服务 + const result = await datasetService.generateDatasetForQuestion(projectId, questionId, { + model, + language + }); + + return NextResponse.json(result); + } catch (error) { + console.error('Failed to generate dataset:', String(error)); + return NextResponse.json( + { + error: error.message || 'Failed to generate dataset' + }, + { status: 500 } + ); + } +} + +/** + * 获取项目的所有数据集 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + const page = parseInt(searchParams.get('page')) || 1; + const size = parseInt(searchParams.get('size')) || 10; + const input = searchParams.get('input'); + const field = searchParams.get('field') || 'question'; + const status = searchParams.get('status'); + const hasCot = searchParams.get('hasCot'); + const isDistill = searchParams.get('isDistill'); + const scoreRange = searchParams.get('scoreRange'); + const customTag = searchParams.get('customTag'); + const noteKeyword = searchParams.get('noteKeyword'); + const chunkName = searchParams.get('chunkName'); + let confirmed = undefined; + if (status === 'confirmed') confirmed = true; + if (status === 'unconfirmed') confirmed = false; + + let selectedAll = searchParams.get('selectedAll'); + + if (selectedAll) { + let data = await getDatasetsIds( + projectId, + confirmed, + input, + field, + hasCot, + isDistill, + scoreRange, + customTag, + noteKeyword, + chunkName + ); + return NextResponse.json(data); + } + + // 获取数据集 + const datasets = await getDatasetsByPagination( + projectId, + page, + size, + confirmed, + input, + field, // 传递搜索字段参数 + hasCot, // 传递思维链筛选参数 + isDistill, // 传递蒸馏数据集筛选参数 + scoreRange, // 传递评分范围筛选参数 + customTag, // 传递自定义标签筛选参数 + noteKeyword, // 传递备注关键字筛选参数 + chunkName // 传递文本块名称筛选参数 + ); + + return NextResponse.json(datasets); + } catch (error) { + console.error('获取数据集失败:', String(error)); + return NextResponse.json( + { + error: error.message || '获取数据集失败' + }, + { status: 500 } + ); + } +} + +/** + * 删除数据集 + */ +export async function DELETE(request) { + try { + const { searchParams } = new URL(request.url); + const datasetId = searchParams.get('id'); + if (!datasetId) { + return NextResponse.json( + { + error: 'Dataset ID cannot be empty' + }, + { status: 400 } + ); + } + + await deleteDataset(datasetId); + + return NextResponse.json({ + success: true, + message: 'Dataset deleted successfully' + }); + } catch (error) { + console.error('Failed to delete dataset:', error); + return NextResponse.json( + { + error: error.message || 'Failed to delete dataset' + }, + { status: 500 } + ); + } +} + +/** + * 编辑数据集 + */ +export async function PATCH(request) { + try { + const { searchParams } = new URL(request.url); + const datasetId = searchParams.get('id'); + const { answer, cot, question, confirmed } = await request.json(); + if (!datasetId) { + return NextResponse.json( + { + error: 'Dataset ID cannot be empty' + }, + { status: 400 } + ); + } + // 获取所有数据集 + let dataset = await getDatasetsById(datasetId); + if (!dataset) { + return NextResponse.json( + { + error: 'Dataset does not exist' + }, + { status: 404 } + ); + } + let data = { id: datasetId }; + if (confirmed !== undefined) data.confirmed = confirmed; + if (answer) data.answer = answer; + if (cot) data.cot = cot; + if (question) data.question = question; + + // 保存更新后的数据集列表 + await updateDataset(data); + + return NextResponse.json({ + success: true, + message: 'Dataset updated successfully', + dataset: dataset + }); + } catch (error) { + console.error('Failed to update dataset:', String(error)); + return NextResponse.json( + { + error: error.message || 'Failed to update dataset' + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/datasets/tags/route.js b/easy-dataset-main/app/api/projects/[projectId]/datasets/tags/route.js new file mode 100644 index 0000000..cf056a0 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/datasets/tags/route.js @@ -0,0 +1,28 @@ +import { NextResponse } from 'next/server'; +import { getUsedCustomTags } from '@/lib/db/datasets'; + +/** + * 获取项目中使用过的自定义标签 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + const tags = await getUsedCustomTags(projectId); + + return NextResponse.json({ tags }); + } catch (error) { + console.error('获取自定义标签失败:', String(error)); + return NextResponse.json( + { + error: error.message || '获取自定义标签失败' + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/default-prompts/route.js b/easy-dataset-main/app/api/projects/[projectId]/default-prompts/route.js new file mode 100644 index 0000000..afe0258 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/default-prompts/route.js @@ -0,0 +1,38 @@ +import { NextResponse } from 'next/server'; + +// 获取默认提示词内容 +export async function GET(request, { params }) { + try { + const { searchParams } = new URL(request.url); + const promptType = searchParams.get('promptType'); + const promptKey = searchParams.get('promptKey'); + + if (!promptType || !promptKey) { + return NextResponse.json({ error: 'promptType and promptKey are required' }, { status: 400 }); + } + + // 动态导入对应的提示词模块 + let promptModule; + try { + promptModule = await import(`@/lib/llm/prompts/${promptType}`); + } catch (error) { + return NextResponse.json({ error: `Prompt module ${promptType} not found` }, { status: 404 }); + } + + // 获取指定的提示词常量 + const promptContent = promptModule[promptKey]; + if (!promptContent) { + return NextResponse.json({ error: `Prompt key ${promptKey} not found in module ${promptType}` }, { status: 404 }); + } + + return NextResponse.json({ + success: true, + content: promptContent, + promptType, + promptKey + }); + } catch (error) { + console.error('获取默认提示词失败:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/distill/questions/by-tag/route.js b/easy-dataset-main/app/api/projects/[projectId]/distill/questions/by-tag/route.js new file mode 100644 index 0000000..421fca0 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/distill/questions/by-tag/route.js @@ -0,0 +1,67 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; + +/** + * 根据标签ID获取问题列表 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const tagId = searchParams.get('tagId'); + + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + if (!tagId) { + return NextResponse.json({ error: '标签ID不能为空' }, { status: 400 }); + } + + // 获取标签信息 + const tag = await db.tags.findUnique({ + where: { id: tagId } + }); + + if (!tag) { + return NextResponse.json({ error: '标签不存在' }, { status: 404 }); + } + + // 获取或创建蒸馏文本块 + let distillChunk = await db.chunks.findFirst({ + where: { + projectId, + name: 'Distilled Content' + } + }); + + if (!distillChunk) { + // 创建一个特殊的蒸馏文本块 + distillChunk = await db.chunks.create({ + data: { + name: 'Distilled Content', + projectId, + fileId: 'distilled', + fileName: 'distilled.md', + content: + 'This text block is used to store questions generated through data distillation and is not related to actual literature.', + summary: 'Questions generated through data distillation', + size: 0 + } + }); + } + const questions = await db.questions.findMany({ + where: { + projectId, + label: tag.label, + chunkId: distillChunk.id + } + }); + + return NextResponse.json(questions); + } catch (error) { + console.error('[distill/questions/by-tag] 获取问题失败:', String(error)); + return NextResponse.json({ error: error.message || '获取问题失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/distill/questions/route.js b/easy-dataset-main/app/api/projects/[projectId]/distill/questions/route.js new file mode 100644 index 0000000..00605ed --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/distill/questions/route.js @@ -0,0 +1,101 @@ +import { NextResponse } from 'next/server'; +import { distillQuestionsPrompt } from '@/lib/llm/prompts/distillQuestions'; +import { db } from '@/lib/db'; + +const LLMClient = require('@/lib/llm/core'); + +/** + * 生成问题接口:根据某个标签链路构造指定数量的问题 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + const { tagPath, currentTag, tagId, count = 5, model, language = 'zh' } = await request.json(); + + if (!currentTag || !tagPath) { + const errorMsg = language === 'en' ? 'Tag information cannot be empty' : '标签信息不能为空'; + return NextResponse.json({ error: errorMsg }, { status: 400 }); + } + + // 首先获取或创建蒸馏文本块 + let distillChunk = await db.chunks.findFirst({ + where: { + projectId, + name: 'Distilled Content' + } + }); + + if (!distillChunk) { + // 创建一个特殊的蒸馏文本块 + distillChunk = await db.chunks.create({ + data: { + name: 'Distilled Content', + projectId, + fileId: 'distilled', + fileName: 'distilled.md', + content: + 'This text block is used to store questions generated through data distillation and is not related to actual literature.', + summary: 'Questions generated through data distillation', + size: 0 + } + }); + } + + // 获取已有的问题,避免重复 + const existingQuestions = await db.questions.findMany({ + where: { + projectId, + label: currentTag, + chunkId: distillChunk.id // 使用蒸馏文本块的 ID + }, + select: { question: true } + }); + + const existingQuestionTexts = existingQuestions.map(q => q.question); + + const llmClient = new LLMClient(model); + const prompt = await distillQuestionsPrompt( + language, + { tagPath, currentTag, count, existingQuestionTexts }, + projectId + ); + const { answer } = await llmClient.getResponseWithCOT(prompt); + + let questions = []; + try { + questions = JSON.parse(answer); + } catch (error) { + console.error('解析问题JSON失败:', String(error)); + // 尝试使用正则表达式提取问题 + const matches = answer.match(/"([^"]+)"/g); + if (matches) { + questions = matches.map(match => match.replace(/"/g, '')); + } + } + + // 保存问题到数据库 + const savedQuestions = []; + for (const questionText of questions) { + const question = await db.questions.create({ + data: { + question: questionText, + projectId, + label: currentTag, + chunkId: distillChunk.id + } + }); + savedQuestions.push(question); + } + + return NextResponse.json(savedQuestions); + } catch (error) { + console.error('生成问题失败:', String(error)); + return NextResponse.json({ error: error.message || '生成问题失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/distill/tags/[tagId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/distill/tags/[tagId]/route.js new file mode 100644 index 0000000..78dae0a --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/distill/tags/[tagId]/route.js @@ -0,0 +1,61 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; + +/** + * 更新标签接口 + */ +export async function PUT(request, { params }) { + try { + const { projectId, tagId } = params; + + // 验证参数 + if (!projectId || !tagId) { + return NextResponse.json({ error: '项目ID和标签ID不能为空' }, { status: 400 }); + } + + const { label } = await request.json(); + + if (!label || !label.trim()) { + return NextResponse.json({ error: '标签名称不能为空' }, { status: 400 }); + } + + // 检查标签是否存在 + const existingTag = await db.tags.findUnique({ + where: { id: tagId } + }); + + if (!existingTag) { + return NextResponse.json({ error: '标签不存在' }, { status: 404 }); + } + + // 检查项目ID是否匹配 + if (existingTag.projectId !== projectId) { + return NextResponse.json({ error: '无权限编辑此标签' }, { status: 403 }); + } + + // 检查新标签名称是否已存在(同级标签) + const duplicateTag = await db.tags.findFirst({ + where: { + projectId, + label: label.trim(), + parentId: existingTag.parentId, + id: { not: tagId } + } + }); + + if (duplicateTag) { + return NextResponse.json({ error: '同级标签名称已存在' }, { status: 400 }); + } + + // 更新标签 + const updatedTag = await db.tags.update({ + where: { id: tagId }, + data: { label: label.trim() } + }); + + return NextResponse.json(updatedTag); + } catch (error) { + console.error('[标签编辑] 更新标签失败:', String(error)); + return NextResponse.json({ error: error.message || '更新标签失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/distill/tags/all/route.js b/easy-dataset-main/app/api/projects/[projectId]/distill/tags/all/route.js new file mode 100644 index 0000000..53de7c1 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/distill/tags/all/route.js @@ -0,0 +1,31 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; + +/** + * 获取项目的所有蒸馏标签 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + // 获取所有标签 + const tags = await db.tags.findMany({ + where: { + projectId + }, + orderBy: { + label: 'asc' + } + }); + + return NextResponse.json(tags); + } catch (error) { + console.error('获取蒸馏标签失败:', String(error)); + return NextResponse.json({ error: error.message || '获取蒸馏标签失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/distill/tags/route.js b/easy-dataset-main/app/api/projects/[projectId]/distill/tags/route.js new file mode 100644 index 0000000..dfa8a36 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/distill/tags/route.js @@ -0,0 +1,88 @@ +import { NextResponse } from 'next/server'; +import { distillTagsPrompt } from '@/lib/llm/prompts/distillTags'; +import { db } from '@/lib/db'; +import { getProject } from '@/lib/db/projects'; + +const LLMClient = require('@/lib/llm/core'); + +/** + * 生成标签接口:根据顶级主题、某级标签构造指定数量的子标签 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + const { parentTag, parentTagId, tagPath, count = 10, model, language = 'zh' } = await request.json(); + + if (!parentTag) { + const errorMsg = language === 'en' ? 'Topic tag name cannot be empty' : '主题标签名称不能为空'; + return NextResponse.json({ error: errorMsg }, { status: 400 }); + } + + // 查询现有标签 + const existingTags = await db.tags.findMany({ + where: { + projectId, + parentId: parentTagId || null + } + }); + + const existingTagNames = existingTags.map(tag => tag.label); + + // 创建LLM客户端 + const llmClient = new LLMClient(model); + + // 生成提示词 + const prompt = await distillTagsPrompt( + language, + { tagPath, parentTag, existingTags: existingTagNames, count }, + projectId + ); + + // 调用大模型生成标签 + const { answer } = await llmClient.getResponseWithCOT(prompt); + + // 解析返回的标签 + let tags = []; + + try { + tags = JSON.parse(answer); + } catch (error) { + console.error('解析标签JSON失败:', String(error)); + // 尝试使用正则表达式提取标签 + const matches = answer.match(/"([^"]+)"/g); + if (matches) { + tags = matches.map(match => match.replace(/"/g, '')); + } + } + + // 保存标签到数据库 + const savedTags = []; + for (let i = 0; i < tags.length; i++) { + const tagName = tags[i]; + try { + const tag = await db.tags.create({ + data: { + label: tagName, + projectId, + parentId: parentTagId || null + } + }); + savedTags.push(tag); + } catch (error) { + console.error(`[标签生成] 保存标签 ${tagName} 失败:`, String(error)); + throw error; + } + } + return NextResponse.json(savedTags); + } catch (error) { + console.error('[标签生成] 生成标签失败:', String(error)); + console.error('[标签生成] 错误堆栈:', error.stack); + return NextResponse.json({ error: error.message || '生成标签失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/[evalId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/[evalId]/route.js new file mode 100644 index 0000000..8a04e3b --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/[evalId]/route.js @@ -0,0 +1,108 @@ +import { NextResponse } from 'next/server'; +import { getEvalQuestionById, updateEvalQuestion, deleteEvalQuestion } from '@/lib/db/evalDatasets'; +import { db } from '@/lib/db/index'; + +/** + * Get evaluation dataset details by ID + * Supports operateType=prev|next to navigate neighbors + */ +export async function GET(request, { params }) { + try { + const { projectId, evalId } = params; + const { searchParams } = new URL(request.url); + const operateType = searchParams.get('operateType'); + + // Navigation request (prev/next) + if (operateType) { + const current = await db.evalDatasets.findUnique({ + where: { id: evalId }, + select: { createAt: true } + }); + + if (!current) { + return NextResponse.json(null); + } + + let neighbor = null; + + if (operateType === 'prev') { + // Get previous item (newer createAt when list is sorted desc) + neighbor = await db.evalDatasets.findFirst({ + where: { + projectId, + createAt: { gt: current.createAt } + }, + orderBy: { createAt: 'asc' }, + select: { id: true } + }); + } else if (operateType === 'next') { + // Get next item (older createAt) + neighbor = await db.evalDatasets.findFirst({ + where: { + projectId, + createAt: { lt: current.createAt } + }, + orderBy: { createAt: 'desc' }, + select: { id: true } + }); + } + + return NextResponse.json(neighbor || null); + } + + // Regular detail request + const evalQuestion = await getEvalQuestionById(evalId); + + if (!evalQuestion) { + return NextResponse.json({ error: 'Eval question not found' }, { status: 404 }); + } + + return NextResponse.json(evalQuestion); + } catch (error) { + console.error('Failed to get eval question:', error); + return NextResponse.json({ error: error.message || 'Failed to get eval question' }, { status: 500 }); + } +} + +/** + * Update evaluation dataset + */ +export async function PUT(request, { params }) { + try { + const { evalId } = params; + const data = await request.json(); + + // Only allow specific fields + const allowedFields = ['question', 'options', 'correctAnswer', 'tags', 'note']; + const updateData = {}; + + for (const field of allowedFields) { + if (data[field] !== undefined) { + updateData[field] = data[field]; + } + } + + const updated = await updateEvalQuestion(evalId, updateData); + + return NextResponse.json(updated); + } catch (error) { + console.error('Failed to update eval question:', error); + return NextResponse.json({ error: error.message || 'Failed to update eval question' }, { status: 500 }); + } +} + +/** + * Delete evaluation dataset + */ +export async function DELETE(request, { params }) { + try { + const { evalId } = params; + + await deleteEvalQuestion(evalId); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Failed to delete eval question:', error); + return NextResponse.json({ error: error.message || 'Failed to delete eval question' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/count/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/count/route.js new file mode 100644 index 0000000..55f5369 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/count/route.js @@ -0,0 +1,63 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; +import { buildEvalQuestionWhere } from '@/lib/db/evalDatasets'; + +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + const questionType = searchParams.get('questionType') || ''; + const keyword = searchParams.get('keyword') || ''; + const chunkId = searchParams.get('chunkId') || ''; + + const questionTypes = searchParams.getAll('questionTypes') || []; + + const tags = + searchParams.getAll('tags').length > 0 + ? searchParams.getAll('tags') + : searchParams.get('tag') + ? searchParams.get('tag').split(',') + : []; + + const where = buildEvalQuestionWhere(projectId, { + questionType: questionType || undefined, + questionTypes: questionTypes.length > 0 ? questionTypes : undefined, + keyword: keyword || undefined, + chunkId: chunkId || undefined, + tags: tags.length > 0 ? tags : undefined + }); + + const [total, byTypeRaw] = await Promise.all([ + db.evalDatasets.count({ where }), + db.evalDatasets.groupBy({ + by: ['questionType'], + where, + _count: { id: true } + }) + ]); + + const byType = {}; + byTypeRaw.forEach(item => { + byType[item.questionType] = item._count.id; + }); + + const hasShortAnswer = (byType.short_answer || 0) > 0; + const hasOpenEnded = (byType.open_ended || 0) > 0; + const hasSubjective = hasShortAnswer || hasOpenEnded; + + return NextResponse.json( + { + code: 0, + data: { total, byType, hasSubjective, hasShortAnswer, hasOpenEnded } + }, + { status: 200 } + ); + } catch (error) { + console.error('Failed to count eval datasets:', error); + return NextResponse.json( + { code: 500, error: 'Failed to count eval datasets', message: error.message }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/export/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/export/route.js new file mode 100644 index 0000000..dd0aab7 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/export/route.js @@ -0,0 +1,231 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import { buildEvalQuestionWhere } from '@/lib/db/evalDatasets'; + +const BATCH_SIZE = 500; + +/** + * Convert an evaluation item to a CSV row + */ +function convertToCSVRow(item, isHeader = false) { + if (isHeader) { + return ['questionType', 'question', 'options', 'correctAnswer', 'tags'].join(','); + } + + const escapeCSV = str => { + if (str === null || str === undefined) return ''; + const strValue = String(str); + if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) { + return `"${strValue.replace(/"/g, '""')}"`; + } + return strValue; + }; + + return [ + escapeCSV(item.questionType), + escapeCSV(item.question), + escapeCSV(item.options), + escapeCSV(item.correctAnswer), + escapeCSV(item.tags) + ].join(','); +} + +/** + * Convert an evaluation item to export format + */ +function formatExportItem(item) { + return { + questionType: item.questionType, + question: item.question, + options: item.options, + correctAnswer: item.correctAnswer, + tags: item.tags + }; +} + +/** + * Export evaluation datasets + * Supports JSON, JSONL, and CSV + * Uses batched streaming for large datasets + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + const { + format = 'json', // json | jsonl | csv + questionTypes = [], + tags = [], + keyword = '' + } = body; + + // Validate format + if (!['json', 'jsonl', 'csv'].includes(format)) { + return NextResponse.json({ code: 400, error: 'Unsupported export format' }, { status: 400 }); + } + + // Build query conditions + const where = buildEvalQuestionWhere(projectId, { + questionTypes: questionTypes.length > 0 ? questionTypes : undefined, + tags: tags.length > 0 ? tags : undefined, + keyword: keyword || undefined + }); + + // Fetch total count + const total = await db.evalDatasets.count({ where }); + + if (total === 0) { + return NextResponse.json({ code: 400, error: 'No data matches the criteria' }, { status: 400 }); + } + + // Return directly for small datasets + if (total <= 1000) { + const items = await db.evalDatasets.findMany({ + where, + orderBy: { createAt: 'desc' } + }); + + const formattedItems = items.map(formatExportItem); + + if (format === 'json') { + return new Response(JSON.stringify(formattedItems, null, 2), { + headers: { + 'Content-Type': 'application/json', + 'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.json"` + } + }); + } + + if (format === 'jsonl') { + const jsonlContent = formattedItems.map(item => JSON.stringify(item)).join('\n'); + return new Response(jsonlContent, { + headers: { + 'Content-Type': 'application/x-ndjson', + 'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.jsonl"` + } + }); + } + + if (format === 'csv') { + const csvContent = [convertToCSVRow(null, true), ...items.map(item => convertToCSVRow(item))].join('\n'); + return new Response('\uFEFF' + csvContent, { + headers: { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.csv"` + } + }); + } + } + + // Stream export for large datasets + const stream = new ReadableStream({ + async start(controller) { + const encoder = new TextEncoder(); + let isFirst = true; + + // CSV outputs header row first + if (format === 'csv') { + controller.enqueue(encoder.encode('\uFEFF' + convertToCSVRow(null, true) + '\n')); + } + + // JSON outputs opening bracket + if (format === 'json') { + controller.enqueue(encoder.encode('[\n')); + } + + // Fetch data in batches + const totalBatches = Math.ceil(total / BATCH_SIZE); + + for (let batch = 0; batch < totalBatches; batch++) { + const items = await db.evalDatasets.findMany({ + where, + orderBy: { createAt: 'desc' }, + skip: batch * BATCH_SIZE, + take: BATCH_SIZE + }); + + for (const item of items) { + const formattedItem = formatExportItem(item); + + if (format === 'json') { + const prefix = isFirst ? '' : ',\n'; + controller.enqueue(encoder.encode(prefix + JSON.stringify(formattedItem))); + isFirst = false; + } else if (format === 'jsonl') { + controller.enqueue(encoder.encode(JSON.stringify(formattedItem) + '\n')); + } else if (format === 'csv') { + controller.enqueue(encoder.encode(convertToCSVRow(item) + '\n')); + } + } + } + + // JSON outputs closing bracket + if (format === 'json') { + controller.enqueue(encoder.encode('\n]')); + } + + controller.close(); + } + }); + + const contentTypes = { + json: 'application/json', + jsonl: 'application/x-ndjson', + csv: 'text/csv; charset=utf-8' + }; + + const extensions = { + json: 'json', + jsonl: 'jsonl', + csv: 'csv' + }; + + return new Response(stream, { + headers: { + 'Content-Type': contentTypes[format], + 'Content-Disposition': `attachment; filename="eval-datasets-${Date.now()}.${extensions[format]}"`, + 'Transfer-Encoding': 'chunked' + } + }); + } catch (error) { + console.error('Failed to export eval datasets:', error); + return NextResponse.json({ code: 500, error: error.message || 'Export failed' }, { status: 500 }); + } +} + +/** + * Get export preview (count only) + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + // Parse query params + const questionTypes = searchParams.getAll('questionTypes'); + const tags = searchParams.getAll('tags'); + const keyword = searchParams.get('keyword') || ''; + + // Build query conditions + const where = buildEvalQuestionWhere(projectId, { + questionTypes: questionTypes.length > 0 ? questionTypes : undefined, + tags: tags.length > 0 ? tags : undefined, + keyword: keyword || undefined + }); + + // Count rows + const total = await db.evalDatasets.count({ where }); + + return NextResponse.json({ + code: 0, + data: { + total, + isLargeDataset: total > 1000 + } + }); + } catch (error) { + console.error('Failed to get export preview:', error); + return NextResponse.json({ code: 500, error: error.message || 'Failed to get export preview' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/import/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/import/route.js new file mode 100644 index 0000000..f7557fa --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/import/route.js @@ -0,0 +1,380 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import { nanoid } from 'nanoid'; +import * as XLSX from 'xlsx'; + +/** + * Validate true/false item schema + */ +function validateTrueFalse(item, index) { + const errors = []; + if (!item.question || typeof item.question !== 'string') { + errors.push(`Item ${index + 1}: missing or invalid "question"`); + } + if (!item.correctAnswer || (item.correctAnswer !== '✅' && item.correctAnswer !== '❌')) { + errors.push(`Item ${index + 1}: "correctAnswer" must be "✅" or "❌"`); + } + return errors; +} + +/** + * Validate single-choice item schema + */ +function validateSingleChoice(item, index) { + const errors = []; + if (!item.question || typeof item.question !== 'string') { + errors.push(`Item ${index + 1}: missing or invalid "question"`); + } + + // Normalize options + let options = item.options; + if (typeof options === 'string') { + try { + options = JSON.parse(options); + } catch (e) { + errors.push(`Item ${index + 1}: invalid "options" format; unable to parse`); + return errors; + } + } + + if (!options || !Array.isArray(options) || options.length < 2) { + errors.push(`Item ${index + 1}: "options" must be an array with at least 2 items`); + } + if (!item.correctAnswer || !/^[A-Z]$/.test(item.correctAnswer)) { + errors.push(`Item ${index + 1}: "correctAnswer" must be a single uppercase letter (A-Z)`); + } + return errors; +} + +/** + * Validate multiple-choice item schema + */ +function validateMultipleChoice(item, index) { + const errors = []; + if (!item.question || typeof item.question !== 'string') { + errors.push(`Item ${index + 1}: missing or invalid "question"`); + } + + // Normalize options + let options = item.options; + if (typeof options === 'string') { + try { + options = JSON.parse(options); + } catch (e) { + errors.push(`Item ${index + 1}: invalid "options" format; unable to parse`); + return errors; + } + } + + if (!options || !Array.isArray(options) || options.length < 2) { + errors.push(`Item ${index + 1}: "options" must be an array with at least 2 items`); + } + + // Normalize correctAnswer + let correctAnswer = item.correctAnswer; + if (typeof correctAnswer === 'string') { + try { + correctAnswer = JSON.parse(correctAnswer); + } catch (e) { + errors.push(`Item ${index + 1}: invalid "correctAnswer" format; unable to parse`); + return errors; + } + } + + if (!correctAnswer || !Array.isArray(correctAnswer) || correctAnswer.length < 1) { + errors.push(`Item ${index + 1}: "correctAnswer" must be an array with at least 1 item`); + } + // Validate each answer token + if (Array.isArray(correctAnswer)) { + for (const ans of correctAnswer) { + if (!/^[A-Z]$/.test(ans)) { + errors.push(`Item ${index + 1}: "${ans}" is not a valid option letter in "correctAnswer"`); + } + } + } + return errors; +} + +/** + * Validate QA item schema (short_answer and open_ended) + */ +function validateQA(item, index) { + const errors = []; + if (!item.question || typeof item.question !== 'string') { + errors.push(`Item ${index + 1}: missing or invalid "question"`); + } + if (!item.correctAnswer || typeof item.correctAnswer !== 'string') { + errors.push(`Item ${index + 1}: missing or invalid "correctAnswer"`); + } + return errors; +} + +/** + * Validate data by question type + */ +function validateData(data, questionType) { + const allErrors = []; + + for (let i = 0; i < data.length; i++) { + let errors = []; + switch (questionType) { + case 'true_false': + errors = validateTrueFalse(data[i], i); + break; + case 'single_choice': + errors = validateSingleChoice(data[i], i); + break; + case 'multiple_choice': + errors = validateMultipleChoice(data[i], i); + break; + case 'short_answer': + case 'open_ended': + errors = validateQA(data[i], i); + break; + default: + errors = [`Unsupported question type: ${questionType}`]; + } + allErrors.push(...errors); + } + + return allErrors; +} + +/** + * Parse an Excel file + */ +function parseExcel(buffer, questionType) { + const excelHeaders = { + question: '\u9898\u76ee', + correctAnswer: '\u6b63\u786e\u7b54\u6848', + answer: '\u7b54\u6848', + options: '\u9009\u9879' + }; + + const workbook = XLSX.read(buffer, { type: 'buffer' }); + const sheetName = workbook.SheetNames[0]; + const sheet = workbook.Sheets[sheetName]; + const rawData = XLSX.utils.sheet_to_json(sheet, { defval: '' }); + + // Convert to normalized schema + const data = rawData.map(row => { + const item = { + question: row.question || row[excelHeaders.question] || '', + correctAnswer: row.correctAnswer || row[excelHeaders.correctAnswer] || row[excelHeaders.answer] || '' + }; + + // Handle options (choice questions) + if (questionType === 'single_choice' || questionType === 'multiple_choice') { + // Try to parse from options column + if (row.options || row[excelHeaders.options]) { + let optionsStr = (row.options || row[excelHeaders.options]).trim(); + + // Replace single quotes so it becomes valid JSON + if (optionsStr.startsWith('[') && optionsStr.includes("'")) { + optionsStr = optionsStr.replace(/'/g, '"'); + } + + try { + // Try JSON parsing + item.options = JSON.parse(optionsStr); + } catch { + // Fallback: split by separators + item.options = optionsStr + .split(/[,;|,;]/) + .map(o => o.trim()) + .filter(Boolean); + } + } + } + + // Handle multiple-choice correctAnswer + if (questionType === 'multiple_choice') { + if (typeof item.correctAnswer === 'string') { + let answerStr = item.correctAnswer.trim(); + + // Replace single quotes so it becomes valid JSON + if (answerStr.startsWith('[') && answerStr.includes("'")) { + answerStr = answerStr.replace(/'/g, '"'); + } + + // Try JSON parsing + try { + item.correctAnswer = JSON.parse(answerStr); + } catch { + // Split string such as "A,B,C" or "ABC" + if (answerStr.includes(',') || answerStr.includes(',')) { + item.correctAnswer = answerStr.split(/[,,]/).map(a => a.trim().toUpperCase()); + } else { + // Split characters such as "ABC" -> ["A", "B", "C"] + item.correctAnswer = answerStr + .toUpperCase() + .split('') + .filter(c => /[A-Z]/.test(c)); + } + } + } + } + + return item; + }); + + return data; +} + +/** + * Parse a JSON file + */ +function parseJSON(content) { + return JSON.parse(content); +} + +/** + * POST - Import evaluation datasets + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const formData = await request.formData(); + + const file = formData.get('file'); + const questionType = formData.get('questionType'); + const tags = formData.get('tags') || ''; + + console.log(`[Import] Start processing. Project: ${projectId}, questionType: ${questionType}, tags: ${tags}`); + + if (!file) { + return NextResponse.json({ code: 400, error: 'Please upload a file' }, { status: 400 }); + } + + if (!questionType) { + return NextResponse.json({ code: 400, error: 'Please select a question type' }, { status: 400 }); + } + + // Validate question type + const validTypes = ['true_false', 'single_choice', 'multiple_choice', 'short_answer', 'open_ended']; + if (!validTypes.includes(questionType)) { + return NextResponse.json({ code: 400, error: `Unsupported question type: ${questionType}` }, { status: 400 }); + } + + // Get file extension + const fileName = file.name; + const fileExt = fileName.split('.').pop().toLowerCase(); + console.log(`[Import] File name: ${fileName}, extension: ${fileExt}`); + + // Validate file type + if (!['json', 'xls', 'xlsx'].includes(fileExt)) { + return NextResponse.json( + { code: 400, error: 'Unsupported file format. Please upload a json, xls, or xlsx file' }, + { status: 400 } + ); + } + + // Read file content + const buffer = await file.arrayBuffer(); + let data = []; + + // Parse file + console.log('[Import] Parsing file...'); + if (fileExt === 'json') { + const content = new TextDecoder().decode(buffer); + data = parseJSON(content); + } else { + data = parseExcel(Buffer.from(buffer), questionType); + } + + console.log(`[Import] Parsing completed. Total items: ${data.length}`); + + if (!Array.isArray(data) || data.length === 0) { + return NextResponse.json({ code: 400, error: 'File is empty or has an invalid format' }, { status: 400 }); + } + + // Validate data + console.log('[Import] Validating data...'); + const errors = validateData(data, questionType); + if (errors.length > 0) { + console.log(`[Import] Validation failed. Error count: ${errors.length}`); + return NextResponse.json( + { + code: 400, + error: 'Data validation failed', + details: errors.slice(0, 10), + totalErrors: errors.length + }, + { status: 400 } + ); + } + + console.log('[Import] Validation passed. Writing to database...'); + + // Prepare data + const now = new Date(); + const evalDatasets = data.map(item => { + // Normalize options + let options = item.options; + if (typeof options === 'string') { + try { + options = JSON.parse(options); + } catch (e) { + // Keep original on parse failure + } + } + + // Normalize correctAnswer + let correctAnswer = item.correctAnswer; + if (typeof correctAnswer === 'string' && questionType === 'multiple_choice') { + try { + correctAnswer = JSON.parse(correctAnswer); + } catch (e) { + // Keep original on parse failure + } + } + + return { + id: nanoid(), + projectId, + question: item.question, + questionType, + options: options ? JSON.stringify(options) : '', + // For multiple_choice, store correctAnswer as JSON array string + correctAnswer: Array.isArray(correctAnswer) ? JSON.stringify(correctAnswer) : correctAnswer, + tags: tags || '', + note: '', + createAt: now, + updateAt: now + }; + }); + + // Batch insert + const batchSize = 100; + let insertedCount = 0; + + for (let i = 0; i < evalDatasets.length; i += batchSize) { + const batch = evalDatasets.slice(i, i + batchSize); + await db.evalDatasets.createMany({ data: batch }); + insertedCount += batch.length; + console.log(`[Import] Inserted ${insertedCount}/${evalDatasets.length} items`); + } + + console.log(`[Import] Import completed. Total inserted: ${insertedCount}`); + + return NextResponse.json({ + code: 0, + data: { + total: insertedCount, + questionType, + tags + }, + message: `Successfully imported ${insertedCount} evaluation items` + }); + } catch (error) { + console.error('[Import] Import failed:', error); + return NextResponse.json( + { + code: 500, + error: 'Import failed', + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/route.js new file mode 100644 index 0000000..69a1ce9 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/route.js @@ -0,0 +1,164 @@ +import { NextResponse } from 'next/server'; +import { getEvalQuestionsWithPagination, getEvalQuestionsStats, deleteEvalQuestion } from '@/lib/db/evalDatasets'; + +/** + * Get project's evaluation dataset list (paginated) + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + // Parse query params + const page = parseInt(searchParams.get('page') || '1', 10); + const pageSize = parseInt(searchParams.get('pageSize') || '20', 10); + const questionType = searchParams.get('questionType') || ''; + const questionTypes = searchParams.getAll('questionTypes'); + const keyword = searchParams.get('keyword') || ''; + const chunkId = searchParams.get('chunkId') || ''; + // Support multiple tags params or comma-separated tag + const tags = + searchParams.getAll('tags').length > 0 + ? searchParams.getAll('tags') + : searchParams.get('tag') + ? searchParams.get('tag').split(',') + : []; + + const includeStats = searchParams.get('includeStats') === 'true'; + + const queryOptions = { + page, + pageSize, + questionType: questionType || undefined, + questionTypes: questionTypes.length > 0 ? questionTypes : undefined, + keyword: keyword || undefined, + chunkId: chunkId || undefined, + tags: tags.length > 0 ? tags : undefined + }; + + if (includeStats) { + const [result, stats] = await Promise.all([ + getEvalQuestionsWithPagination(projectId, queryOptions), + getEvalQuestionsStats(projectId) + ]); + result.stats = stats; + return NextResponse.json(result); + } + + const result = await getEvalQuestionsWithPagination(projectId, queryOptions); + return NextResponse.json(result); + } catch (error) { + console.error('Failed to get eval datasets:', error); + return NextResponse.json({ error: error.message || 'Failed to get eval datasets' }, { status: 500 }); + } +} + +/** + * Batch delete evaluation datasets + */ +export async function DELETE(request, { params }) { + try { + const { ids } = await request.json(); + + if (!ids || !Array.isArray(ids) || ids.length === 0) { + return NextResponse.json({ error: 'Invalid request: ids array is required' }, { status: 400 }); + } + + const results = await Promise.all(ids.map(id => deleteEvalQuestion(id).catch(err => ({ error: err.message, id })))); + const deleted = results.filter(r => !r.error).length; + const failed = results.filter(r => r.error).length; + + return NextResponse.json({ + success: true, + deleted, + failed, + message: `Successfully deleted ${deleted} items${failed > 0 ? `, ${failed} failed` : ''}` + }); + } catch (error) { + console.error('Failed to delete eval datasets:', error); + return NextResponse.json({ error: error.message || 'Failed to delete eval datasets' }, { status: 500 }); + } +} + +/** + * Create a new evaluation dataset (or batch create) + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + const { createEvalQuestion, createManyEvalQuestions } = require('@/lib/db/evalDatasets'); + + // Handle batch creation + if (Array.isArray(body) || (body.items && Array.isArray(body.items))) { + const items = Array.isArray(body) ? body : body.items; + + if (items.length === 0) { + return NextResponse.json({ success: true, count: 0 }); + } + + // Validate items + const validItems = items + .map(item => { + // 确保标签格式正确: 数组转为逗号分隔字符串 + let tagsStr = item.tags || ''; + if (Array.isArray(tagsStr)) { + tagsStr = tagsStr.join(','); + } + return { + projectId, + question: item.question, + questionType: item.questionType || 'open_ended', + correctAnswer: + typeof item.correctAnswer === 'object' ? JSON.stringify(item.correctAnswer) : item.correctAnswer, + tags: tagsStr, + note: item.note || '', + chunkId: item.chunkId || null, + options: item.options + ? typeof item.options === 'object' + ? JSON.stringify(item.options) + : item.options + : '' + }; + }) + .filter(item => item.question && item.correctAnswer); + + if (validItems.length === 0) { + return NextResponse.json({ error: 'No valid items to create' }, { status: 400 }); + } + + const result = await createManyEvalQuestions(validItems); + return NextResponse.json({ success: true, count: result.count }); + } + + // Handle single creation + const { question, correctAnswer, questionType = 'open_ended', tags, note, chunkId, options } = body; + + if (!question || !correctAnswer) { + return NextResponse.json({ error: 'Question and Correct Answer are required' }, { status: 400 }); + } + + // 确保标签格式正确: 数组转为逗号分隔字符串 + let tagsStr = tags || ''; + if (Array.isArray(tagsStr)) { + tagsStr = tagsStr.join(','); + } + + const evalDataset = await createEvalQuestion({ + projectId, + question, + questionType, + correctAnswer: typeof correctAnswer === 'object' ? JSON.stringify(correctAnswer) : correctAnswer, + tags: tagsStr, + note: note || '', + chunkId: chunkId || null, + options: options ? (typeof options === 'object' ? JSON.stringify(options) : options) : '' + }); + + return NextResponse.json({ success: true, evalDataset }); + } catch (error) { + console.error('Failed to create eval dataset:', error); + return NextResponse.json({ error: error.message || 'Failed to create eval dataset' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/sample/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/sample/route.js new file mode 100644 index 0000000..74113ad --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/sample/route.js @@ -0,0 +1,124 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db'; +import { buildEvalQuestionWhere } from '@/lib/db/evalDatasets'; + +const SMALL_TOTAL_THRESHOLD = 5000; +const HARD_LIMIT = 50000; + +function shuffleArray(arr) { + const result = [...arr]; + for (let i = result.length - 1; i > 0; i -= 1) { + const j = Math.floor(Math.random() * (i + 1)); + [result[i], result[j]] = [result[j], result[i]]; + } + return result; +} + +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + const { + questionType = '', + questionTypes = [], + keyword = '', + chunkId = '', + tags = [], + limit = 0, + strategy = 'random' + } = body || {}; + + const where = buildEvalQuestionWhere(projectId, { + questionType: questionType || undefined, + questionTypes: Array.isArray(questionTypes) && questionTypes.length > 0 ? questionTypes : undefined, + keyword: keyword || undefined, + chunkId: chunkId || undefined, + tags: Array.isArray(tags) && tags.length > 0 ? tags : undefined + }); + + const total = await db.evalDatasets.count({ where }); + + if (total === 0) { + return NextResponse.json( + { + code: 0, + data: { + total: 0, + selectedCount: 0, + ids: [], + strategyUsed: strategy + } + }, + { status: 200 } + ); + } + + let normalizedLimit = typeof limit === 'number' && limit > 0 ? Math.min(limit, HARD_LIMIT) : HARD_LIMIT; + + if (normalizedLimit >= total) { + const items = await db.evalDatasets.findMany({ + where, + select: { id: true }, + orderBy: { createAt: 'desc' } + }); + + const ids = items.map(item => item.id); + + return NextResponse.json( + { + code: 0, + data: { + total, + selectedCount: ids.length, + ids, + strategyUsed: total > HARD_LIMIT ? 'top' : strategy + } + }, + { status: 200 } + ); + } + + let ids = []; + let strategyUsed = strategy; + + if (total <= SMALL_TOTAL_THRESHOLD) { + const items = await db.evalDatasets.findMany({ + where, + select: { id: true }, + orderBy: { createAt: 'desc' } + }); + const shuffled = shuffleArray(items); + ids = shuffled.slice(0, normalizedLimit).map(item => item.id); + strategyUsed = 'random-small'; + } else { + const items = await db.evalDatasets.findMany({ + where, + select: { id: true }, + orderBy: { createAt: 'desc' }, + take: normalizedLimit + }); + ids = items.map(item => item.id); + strategyUsed = 'top-latest'; + } + + return NextResponse.json( + { + code: 0, + data: { + total, + selectedCount: ids.length, + ids, + strategyUsed + } + }, + { status: 200 } + ); + } catch (error) { + console.error('Failed to sample eval datasets:', error); + return NextResponse.json( + { code: 500, error: 'Failed to sample eval datasets', message: error.message }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/tags/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/tags/route.js new file mode 100644 index 0000000..4cef0c5 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-datasets/tags/route.js @@ -0,0 +1,35 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; + +/** + * Get all evaluation dataset tags in the project + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // Fetch tags for all datasets in the project + const datasets = await db.evalDatasets.findMany({ + where: { projectId }, + select: { tags: true } + }); + + // Extract and de-duplicate tags + const tagsSet = new Set(); + datasets.forEach(dataset => { + if (dataset.tags) { + // Support both English and Chinese commas + const tags = dataset.tags + .split(/[,,]/) + .map(t => t.trim()) + .filter(Boolean); + tags.forEach(tag => tagsSet.add(tag)); + } + }); + + return NextResponse.json({ tags: Array.from(tagsSet).sort() }); + } catch (error) { + console.error('Failed to get tags:', error); + return NextResponse.json({ error: error.message || 'Failed to get tags' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-tasks/[taskId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-tasks/[taskId]/route.js new file mode 100644 index 0000000..f7ebf7b --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-tasks/[taskId]/route.js @@ -0,0 +1,176 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import { getEvalResultsByTaskId, getEvalResultsStats } from '@/lib/db/evalResults'; + +/** + * Get evaluation task details and results + */ +export async function GET(request, { params }) { + try { + const { projectId, taskId } = params; + + if (!projectId || !taskId) { + return NextResponse.json({ error: 'Project ID and Task ID are required' }, { status: 400 }); + } + + // Fetch task details + const task = await db.task.findUnique({ + where: { id: taskId } + }); + + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + if (task.projectId !== projectId) { + return NextResponse.json({ error: 'Task does not belong to this project' }, { status: 403 }); + } + + // Parse task detail fields + let detail = {}; + let modelInfo = {}; + try { + detail = task.detail ? JSON.parse(task.detail) : {}; + modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {}; + } catch (e) { + console.error('Failed to parse task detail:', e); + } + + // Parse query params + const { searchParams } = new URL(request.url); + const page = parseInt(searchParams.get('page') || '1'); + const pageSize = parseInt(searchParams.get('pageSize') || '10'); + const type = searchParams.get('type') || null; + const isCorrectStr = searchParams.get('isCorrect'); + const isCorrect = isCorrectStr === 'true' ? true : isCorrectStr === 'false' ? false : null; + + // Fetch results (supports pagination and filters) + const { items: results, total } = await getEvalResultsByTaskId(taskId, { + page, + pageSize, + type, + isCorrect + }); + + // Fetch stats + const stats = await getEvalResultsStats(taskId); + + return NextResponse.json({ + code: 0, + data: { + task: { + ...task, + detail, + modelInfo + }, + results, + total, + page, + pageSize, + stats + } + }); + } catch (error) { + console.error('Failed to fetch evaluation task details:', error); + return NextResponse.json( + { code: 500, error: 'Failed to fetch evaluation task details', message: error.message }, + { status: 500 } + ); + } +} + +/** + * Delete evaluation task + */ +export async function DELETE(request, { params }) { + try { + const { projectId, taskId } = params; + + if (!projectId || !taskId) { + return NextResponse.json({ error: 'Project ID and Task ID are required' }, { status: 400 }); + } + + // Validate task exists and belongs to this project + const task = await db.task.findUnique({ + where: { id: taskId } + }); + + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + if (task.projectId !== projectId) { + return NextResponse.json({ error: 'Task does not belong to this project' }, { status: 403 }); + } + + // Delete evaluation results + await db.evalResults.deleteMany({ + where: { taskId } + }); + + // Delete task + await db.task.delete({ + where: { id: taskId } + }); + + return NextResponse.json({ + code: 0, + message: 'Deleted' + }); + } catch (error) { + console.error('Failed to delete evaluation task:', error); + return NextResponse.json( + { code: 500, error: 'Failed to delete evaluation task', message: error.message }, + { status: 500 } + ); + } +} + +/** + * Interrupt evaluation task + */ +export async function PUT(request, { params }) { + try { + const { projectId, taskId } = params; + const data = await request.json(); + const { action } = data; + + if (!projectId || !taskId) { + return NextResponse.json({ error: 'Project ID and Task ID are required' }, { status: 400 }); + } + + // Validate task exists and belongs to this project + const task = await db.task.findUnique({ + where: { id: taskId } + }); + + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + if (task.projectId !== projectId) { + return NextResponse.json({ error: 'Task does not belong to this project' }, { status: 403 }); + } + + if (action === 'interrupt') { + // Interrupt task + await db.task.update({ + where: { id: taskId }, + data: { + status: 3, // Interrupted + endTime: new Date() + } + }); + + return NextResponse.json({ + code: 0, + message: 'Task interrupted' + }); + } + + return NextResponse.json({ error: 'Unknown action' }, { status: 400 }); + } catch (error) { + console.error('Failed to operate evaluation task:', error); + return NextResponse.json({ code: 500, error: 'Operation failed', message: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/eval-tasks/route.js b/easy-dataset-main/app/api/projects/[projectId]/eval-tasks/route.js new file mode 100644 index 0000000..9984b05 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/eval-tasks/route.js @@ -0,0 +1,207 @@ +import { NextResponse } from 'next/server'; +import { db } from '@/lib/db/index'; +import { processTask } from '@/lib/services/tasks'; + +/** + * Get all evaluation tasks for a project + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const page = parseInt(searchParams.get('page') || '1'); + const pageSize = parseInt(searchParams.get('pageSize') || '20'); + + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + const skip = (page - 1) * pageSize; + + // Fetch task list and total count + const [tasks, total] = await Promise.all([ + db.task.findMany({ + where: { + projectId, + taskType: 'model-evaluation' + }, + orderBy: { createAt: 'desc' }, + skip, + take: pageSize + }), + db.task.count({ + where: { + projectId, + taskType: 'model-evaluation' + } + }) + ]); + + // Parse task detail fields + const tasksWithDetails = tasks.map(task => { + let detail = {}; + let modelInfo = {}; + try { + detail = task.detail ? JSON.parse(task.detail) : {}; + modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {}; + } catch (e) { + console.error('Failed to parse task detail:', e); + } + return { + ...task, + detail, + modelInfo + }; + }); + + return NextResponse.json({ + code: 0, + data: { + items: tasksWithDetails, + total, + page, + pageSize, + totalPages: Math.ceil(total / pageSize) + } + }); + } catch (error) { + console.error('Failed to fetch evaluation task list:', error); + return NextResponse.json( + { code: 500, error: 'Failed to fetch evaluation task list', message: error.message }, + { status: 500 } + ); + } +} + +/** + * Create evaluation tasks + * Supports selecting multiple models and creating one task per model + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const data = await request.json(); + + const { + models, // Models to evaluate: [{ modelId, providerId }] + evalDatasetIds, // Evaluation question IDs + judgeModelId, // Judge model ID (for subjective grading) + judgeProviderId, // Judge provider ID + language = 'zh-CN', + filterOptions = {}, // Filter options (for display) + customScoreAnchors = null // Custom score anchors for subjective grading + } = data; + + // Validate required fields + if (!models || models.length === 0) { + return NextResponse.json({ code: 400, error: 'Please select at least one model to evaluate' }, { status: 400 }); + } + + if (!evalDatasetIds || evalDatasetIds.length === 0) { + return NextResponse.json({ code: 400, error: 'Please select questions to evaluate' }, { status: 400 }); + } + + // Check for subjective questions + const evalDatasets = await db.evalDatasets.findMany({ + where: { + id: { in: evalDatasetIds }, + projectId + }, + select: { questionType: true } + }); + + const hasSubjectiveQuestions = evalDatasets.some( + q => q.questionType === 'short_answer' || q.questionType === 'open_ended' + ); + + // If there are subjective questions, a judge model is required + if (hasSubjectiveQuestions && (!judgeModelId || !judgeProviderId)) { + return NextResponse.json( + { code: 400, error: 'Short-answer or open-ended questions found. Please select a judge model for grading' }, + { status: 400 } + ); + } + + // Judge model must not be the same as any test model + if (judgeModelId && judgeProviderId) { + const judgeModel = { modelId: judgeModelId, providerId: judgeProviderId }; + const isJudgeInTestModels = models.some( + m => m.modelId === judgeModel.modelId && m.providerId === judgeModel.providerId + ); + if (isJudgeInTestModels) { + return NextResponse.json( + { code: 400, error: 'Judge model cannot be the same as a test model' }, + { status: 400 } + ); + } + } + + // Create one task per model + const createdTasks = []; + + for (const model of models) { + const { modelId, providerId } = model; + + // Fetch full model config + const modelConfig = await db.modelConfig.findFirst({ + where: { + projectId, + providerId, + modelId + } + }); + + // Keep providerId for lookup, add providerName for display + const modelInfo = { + modelId, + modelName: modelConfig?.modelName || modelId, + providerId: providerId, // Provider ID (DB ID) + providerName: modelConfig?.providerName || providerId // Provider display name + }; + + // Build task detail + const taskDetail = { + evalDatasetIds, + judgeModelId: judgeModelId || null, + judgeProviderId: judgeProviderId || null, + filterOptions, + hasSubjectiveQuestions, + customScoreAnchors: customScoreAnchors || null // Store custom score anchors + }; + + // Create task + const newTask = await db.task.create({ + data: { + projectId, + taskType: 'model-evaluation', + status: 0, // Processing + modelInfo: JSON.stringify(modelInfo), + language, + detail: JSON.stringify(taskDetail), + totalCount: evalDatasetIds.length, + completedCount: 0, + note: '' + } + }); + + createdTasks.push(newTask); + + // Start task processing asynchronously + processTask(newTask.id).catch(err => { + console.error(`Failed to start evaluation task: ${newTask.id}`, err); + }); + } + + return NextResponse.json({ + code: 0, + data: createdTasks, + message: `Successfully created ${createdTasks.length} evaluation tasks` + }); + } catch (error) { + console.error('Failed to create evaluation task:', error); + return NextResponse.json( + { code: 500, error: 'Failed to create evaluation task', message: error.message }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/files/[fileId]/ga-pairs/route.js b/easy-dataset-main/app/api/projects/[projectId]/files/[fileId]/ga-pairs/route.js new file mode 100644 index 0000000..3b6aed5 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/files/[fileId]/ga-pairs/route.js @@ -0,0 +1,313 @@ +import { NextResponse } from 'next/server'; +import { getGaPairsByFileId, toggleGaPairActive, saveGaPairs, createGaPairs } from '@/lib/db/ga-pairs'; +import { getUploadFileInfoById } from '@/lib/db/upload-files'; +import { generateGaPairs } from '@/lib/services/ga/ga-generation'; +import logger from '@/lib/util/logger'; +import { db } from '@/lib/db/index'; + +/** + * 生成文件的 GA 对 + */ +export async function POST(request, { params }) { + try { + const { projectId, fileId } = params; + const { regenerate = false, appendMode = false, language = '中文' } = await request.json(); + + // 验证参数 + if (!projectId || !fileId) { + return NextResponse.json({ error: 'Project ID and File ID are required' }, { status: 400 }); + } + + logger.info(`Starting GA pairs generation for project: ${projectId}, file: ${fileId}, appendMode: ${appendMode}`); + + // 检查文件是否存在 + const file = await getUploadFileInfoById(fileId); + if (!file || file.projectId !== projectId) { + return NextResponse.json({ error: 'File not found or does not belong to the project' }, { status: 404 }); + } + + // 获取现有的GA对 + const existingGaPairs = await getGaPairsByFileId(fileId); + + // 如果是追加模式且已有GA对,或者不是重新生成且已存在GA对 + if (!regenerate && !appendMode && existingGaPairs.length > 0) { + return NextResponse.json({ + success: true, + message: 'GA pairs already exist for this file', + data: existingGaPairs + }); + } + + // 读取文件内容 + const fileContent = await getFileContent(projectId, file.fileName); + if (!fileContent) { + return NextResponse.json({ error: 'Failed to read file content' }, { status: 500 }); + } + + logger.info(`File content loaded successfully, length: ${fileContent.length}`); + + // 检查模型配置 + try { + const { getActiveModel } = await import('@/lib/services/models'); + const activeModel = await getActiveModel(projectId); + + if (!activeModel) { + logger.error('No active model configuration found'); + return NextResponse.json( + { error: 'No active AI model configured. Please configure a model in settings first.' }, + { status: 400 } + ); + } + + logger.info(`Using active model: ${activeModel.provider} - ${activeModel.model}`); + } catch (modelError) { + logger.error('Error checking model configuration:', modelError); + return NextResponse.json( + { error: 'Failed to load model configuration. Please check your AI model settings.' }, + { status: 500 } + ); + } + + // 调用 LLM 生成 GA 对 + logger.info(`Generating GA pairs for file: ${file.fileName}`); + let generatedGaPairs; + + try { + generatedGaPairs = await generateGaPairs(fileContent, projectId, language); + + if (!generatedGaPairs || generatedGaPairs.length === 0) { + logger.warn('No GA pairs generated from LLM'); + return NextResponse.json( + { + error: + 'No GA pairs could be generated from the file content. The content might be too short or not suitable for GA pair generation.' + }, + { status: 400 } + ); + } + + logger.info(`Successfully generated ${generatedGaPairs.length} GA pairs from LLM`); + } catch (generationError) { + logger.error('GA pairs generation failed:', generationError); + + // 现有的错误处理逻辑... + let errorMessage = 'Failed to generate GA pairs'; + if (generationError.message.includes('No active model')) { + errorMessage = 'No active AI model available. Please configure and activate a model in settings.'; + } else if (generationError.message.includes('API key')) { + errorMessage = 'Invalid API key or model configuration. Please check your AI model settings.'; + } else if (generationError.message.includes('rate limit')) { + errorMessage = 'API rate limit exceeded. Please try again later.'; + } else { + errorMessage = `AI model error: ${generationError.message}`; + } + + return NextResponse.json({ error: errorMessage }, { status: 500 }); + } + + // 保存到数据库 + try { + if (appendMode && existingGaPairs.length > 0) { + // 追加模式:只保存新生成的GA对,不删除现有的 + logger.info(`Appending ${generatedGaPairs.length} new GA pairs to existing ${existingGaPairs.length} pairs`); + + // 为新GA对设置正确的pairNumber + const startPairNumber = existingGaPairs.length + 1; + const newGaPairData = generatedGaPairs.map((pair, index) => ({ + projectId, + fileId, + pairNumber: startPairNumber + index, + genreTitle: pair.genre?.title || pair.genreTitle || '', + genreDesc: pair.genre?.description || pair.genreDesc || '', + audienceTitle: pair.audience?.title || pair.audienceTitle || '', + audienceDesc: pair.audience?.description || pair.audienceDesc || '', + isActive: true + })); + + // 只创建新的GA对,不删除现有的 + await createGaPairs(newGaPairData); + logger.info('New GA pairs appended to database successfully'); + } else { + // 覆盖模式:删除现有的,保存新的 + await saveGaPairs(projectId, fileId, generatedGaPairs); + logger.info('GA pairs saved to database successfully'); + } + } catch (saveError) { + logger.error('Failed to save GA pairs to database:', saveError); + return NextResponse.json( + { error: 'Generated GA pairs successfully but failed to save to database' }, + { status: 500 } + ); + } + + // 获取保存后的所有GA对 + const allGaPairs = await getGaPairsByFileId(fileId); + + if (appendMode && existingGaPairs.length > 0) { + // 追加模式:只返回新生成的GA对 + const newGaPairs = allGaPairs.slice(existingGaPairs.length); + logger.info(`Successfully appended ${newGaPairs.length} GA pairs. Total pairs: ${allGaPairs.length}`); + + return NextResponse.json({ + success: true, + message: `${newGaPairs.length} new GA pairs appended successfully`, + data: newGaPairs, + total: allGaPairs.length + }); + } else { + // 覆盖模式:返回所有GA对 + logger.info(`Successfully generated and saved ${allGaPairs.length} GA pairs for file: ${file.fileName}`); + + return NextResponse.json({ + success: true, + message: 'GA pairs generated successfully', + data: allGaPairs + }); + } + } catch (error) { + logger.error('Unexpected error in GA pairs generation:', error); + return NextResponse.json( + { error: error.message || 'Unexpected error occurred during GA pairs generation' }, + { status: 500 } + ); + } +} + +/** + * 获取文件的 GA 对 + */ +export async function GET(request, { params }) { + try { + const { projectId, fileId } = params; + + if (!projectId || !fileId) { + return NextResponse.json({ error: 'Project ID and File ID are required' }, { status: 400 }); + } + + const gaPairs = await getGaPairsByFileId(fileId); + + return NextResponse.json({ + success: true, + data: gaPairs + }); + } catch (error) { + console.error('Error getting GA pairs:', String(error)); + return NextResponse.json({ error: 'Failed to get GA pairs' }, { status: 500 }); + } +} + +/** + * 更新/替换文件的所有 GA 对 + */ +export async function PUT(request, { params }) { + try { + const { projectId, fileId } = params; + const body = await request.json(); + + if (!projectId || !fileId) { + return NextResponse.json({ error: 'Project ID and File ID are required' }, { status: 400 }); + } + + const { updates } = body; + + if (!updates || !Array.isArray(updates)) { + return NextResponse.json({ error: 'Updates array is required' }, { status: 400 }); + } + + logger.info(`Replacing all GA pairs for file ${fileId} with ${updates.length} pairs`); + + // 使用数据库事务确保原子性操作 + const results = await db.$transaction(async tx => { + // 1. 先删除所有现有的GA对 + await tx.gaPairs.deleteMany({ + where: { fileId } + }); + + // 2. 然后创建新的GA对 + if (updates.length > 0) { + const gaPairData = updates.map((pair, index) => ({ + projectId, + fileId, + pairNumber: index + 1, + genreTitle: pair.genreTitle || pair.genre?.title || pair.genre || '', + genreDesc: pair.genreDesc || pair.genre?.description || '', + audienceTitle: pair.audienceTitle || pair.audience?.title || pair.audience || '', + audienceDesc: pair.audienceDesc || pair.audience?.description || '', + isActive: pair.isActive !== undefined ? pair.isActive : true + })); + + // 验证数据 + for (const data of gaPairData) { + if (!data.genreTitle || !data.audienceTitle) { + throw new Error(`Invalid GA pair data: missing genre or audience title`); + } + } + + await tx.gaPairs.createMany({ data: gaPairData }); + } + + // 3. 返回新创建的GA对 + return await tx.gaPairs.findMany({ + where: { fileId }, + orderBy: { pairNumber: 'asc' } + }); + }); + + logger.info(`Successfully replaced GA pairs, new count: ${results.length}`); + + return NextResponse.json({ + success: true, + data: results + }); + } catch (error) { + logger.error('Error updating GA pairs:', error); + return NextResponse.json({ error: error.message || 'Failed to update GA pairs' }, { status: 500 }); + } +} + +/** + * 切换 GA 对激活状态 + */ +export async function PATCH(request, { params }) { + try { + const { projectId, fileId } = params; + const body = await request.json(); + + if (!projectId || !fileId) { + return NextResponse.json({ error: 'Project ID and File ID are required' }, { status: 400 }); + } + + const { gaPairId, isActive } = body; + + if (!gaPairId || typeof isActive !== 'boolean') { + return NextResponse.json({ error: 'GA pair ID and active status are required' }, { status: 400 }); + } + + const updatedPair = await toggleGaPairActive(gaPairId, isActive); + + return NextResponse.json({ + success: true, + data: updatedPair + }); + } catch (error) { + console.error('Error toggling GA pair active status:', String(error)); + return NextResponse.json({ error: 'Failed to toggle GA pair active status' }, { status: 500 }); + } +} + +// Helper function to read file content +async function getFileContent(projectId, fileName) { + try { + const { getProjectRoot } = await import('@/lib/db/base'); + const path = await import('path'); + const fs = await import('fs'); + + const projectRoot = await getProjectRoot(); + const filePath = path.join(projectRoot, projectId, 'files', fileName.replace('.pdf', '.md')); + + return await fs.promises.readFile(filePath, 'utf8'); + } catch (error) { + logger.error('Failed to read file content:', error); + return null; + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/files/route.js b/easy-dataset-main/app/api/projects/[projectId]/files/route.js new file mode 100644 index 0000000..2374196 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/files/route.js @@ -0,0 +1,243 @@ +import { NextResponse } from 'next/server'; +import { getProject } from '@/lib/db/projects'; +import path from 'path'; +import { getProjectRoot, ensureDir } from '@/lib/db/base'; +import { promises as fs } from 'fs'; +import { + checkUploadFileInfoByMD5, + createUploadFileInfo, + delUploadFileInfoById, + getUploadFilesPagination +} from '@/lib/db/upload-files'; +import { getFileMD5 } from '@/lib/util/file'; +import { batchSaveTags } from '@/lib/db/tags'; +import { getProjectChunks, getProjectTocByName } from '@/lib/file/text-splitter'; +import { handleDomainTree } from '@/lib/util/domain-tree'; + +// Replace the deprecated config export with the new export syntax +export const dynamic = 'force-dynamic'; +// This tells Next.js not to parse the request body automatically +export const bodyParser = false; + +// 获取项目文件列表 +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + const { searchParams } = new URL(request.url); + const page = parseInt(searchParams.get('page')) || 1; + const pageSize = parseInt(searchParams.get('pageSize')) || 10; // 每页10个文件,支持分页 + const fileName = searchParams.get('fileName') || ''; + const getAllIds = searchParams.get('getAllIds') === 'true'; // 新增:获取所有文件ID的标志 + + // 如果请求所有文件ID,直接返回ID列表 + if (getAllIds) { + const allFiles = await getUploadFilesPagination(projectId, 1, 9999, fileName); // 获取所有文件 + const allFileIds = allFiles.data?.map(file => String(file.id)) || []; + return NextResponse.json({ allFileIds }); + } + // 获取文件列表 + const files = await getUploadFilesPagination(projectId, page, pageSize, fileName); + + return NextResponse.json(files); + } catch (error) { + console.error('Error obtaining file list:', String(error)); + return NextResponse.json({ error: error.message || 'Error obtaining file list' }, { status: 500 }); + } +} + +// 删除文件 +export async function DELETE(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const fileId = searchParams.get('fileId'); + const domainTreeAction = searchParams.get('domainTreeAction') || 'keep'; + + // 从请求体中获取模型信息和语言环境 + const requestData = await request.json(); + const model = requestData.model; + const language = requestData.language || 'en'; + + // 验证项目ID和文件名 + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + if (!fileId) { + return NextResponse.json({ error: 'The file name cannot be empty' }, { status: 400 }); + } + + // 获取项目信息 + const project = await getProject(projectId); + if (!project) { + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + + // 删除文件及其相关的文本块、问题和数据集 + const { stats, fileName, fileInfo } = await delUploadFileInfoById(fileId); + const deleteToc = await getProjectTocByName(projectId, fileName); + try { + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + const tocDir = path.join(projectPath, 'toc'); + const baseName = path.basename(fileInfo.fileName, path.extname(fileInfo.fileName)); + const tocPath = path.join(tocDir, `${baseName}-toc.json`); + + // 检查文件是否存在再删除 + await fs.unlink(tocPath); + console.log(`成功删除 TOC 文件: ${tocPath}`); + } catch (error) { + console.error(`删除 TOC 文件失败:`, String(error)); + // 即使 TOC 文件删除失败,不影响整体结果 + } + + // 如果选择了保持领域树不变,直接返回删除结果 + if (domainTreeAction === 'keep') { + return NextResponse.json({ + message: '文件删除成功', + stats: stats, + domainTreeAction: 'keep', + cascadeDelete: true + }); + } + + // 处理领域树更新 + try { + // 获取项目的所有文件 + const { chunks, toc } = await getProjectChunks(projectId); + + // 如果不存在文本块,说明项目已经没有文件了 + if (!chunks || chunks.length === 0) { + // 清空领域树 + await batchSaveTags(projectId, []); + return NextResponse.json({ + message: '文件删除成功,领域树已清空', + stats: stats, + domainTreeAction, + cascadeDelete: true + }); + } + + // 调用领域树处理模块 + await handleDomainTree({ + projectId, + action: domainTreeAction, + allToc: toc, + model, + language, + deleteToc, + project + }); + } catch (error) { + console.error('Error updating domain tree after file deletion:', String(error)); + // 即使领域树更新失败,也不影响文件删除的结果 + } + + return NextResponse.json({ + message: '文件删除成功', + stats: stats, + domainTreeAction, + cascadeDelete: true + }); + } catch (error) { + console.error('Error deleting file:', String(error)); + return NextResponse.json({ error: error.message || 'Error deleting file' }, { status: 500 }); + } +} + +// 上传文件 +export async function POST(request, { params }) { + console.log('File upload request processing, parameters:', params); + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + console.log('The project ID cannot be empty, returning 400 error'); + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + // 获取项目信息 + const project = await getProject(projectId); + if (!project) { + console.log('The project does not exist, returning 404 error'); + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + console.log('Project information retrieved successfully:', project.name || project.id); + + try { + console.log('Try using alternate methods for file upload...'); + + // 检查请求头中是否包含文件名 + const encodedFileName = request.headers.get('x-file-name'); + const fileName = encodedFileName ? decodeURIComponent(encodedFileName) : null; + console.log('Get file name from request header:', fileName); + + if (!fileName) { + console.log('The request header does not contain a file name'); + return NextResponse.json( + { error: 'The request header does not contain a file name (x-file-name)' }, + { status: 400 } + ); + } + + // 检查文件类型 + if (!fileName.endsWith('.md') && !fileName.endsWith('.pdf')) { + return NextResponse.json({ error: 'Only Markdown files are supported' }, { status: 400 }); + } + + // 直接从请求体中读取二进制数据 + const fileBuffer = Buffer.from(await request.arrayBuffer()); + + // 保存文件 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + const filesDir = path.join(projectPath, 'files'); + + await ensureDir(filesDir); + + const filePath = path.join(filesDir, fileName); + await fs.writeFile(filePath, fileBuffer); + //获取文件大小 + const stats = await fs.stat(filePath); + //获取文件md5 + const md5 = await getFileMD5(filePath); + //获取文件扩展名 + const ext = path.extname(filePath); + + // let res = await checkUploadFileInfoByMD5(projectId, md5); + // if (res) { + // return NextResponse.json({ error: `【${fileName}】该文件已在此项目中存在` }, { status: 400 }); + // } + + let fileInfo = await createUploadFileInfo({ + projectId, + fileName, + size: stats.size, + md5, + fileExt: ext, + path: filesDir + }); + + console.log('The file upload process is complete, and a successful response is returned'); + return NextResponse.json({ + message: 'File uploaded successfully', + fileName, + filePath, + fileId: fileInfo.id + }); + } catch (error) { + console.error('Error processing file upload:', String(error)); + console.error('Error stack:', error.stack); + return NextResponse.json( + { + error: 'File upload failed: ' + (error.message || 'Unknown error') + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/generate-questions/route.js b/easy-dataset-main/app/api/projects/[projectId]/generate-questions/route.js new file mode 100644 index 0000000..5c8bab4 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/generate-questions/route.js @@ -0,0 +1,126 @@ +import { NextResponse } from 'next/server'; +import { getProjectChunks } from '@/lib/file/text-splitter'; +import { getTaskConfig } from '@/lib/db/projects'; +import { getChunkById } from '@/lib/db/chunks'; +import { generateQuestionsForChunk, generateQuestionsForChunkWithGA } from '@/lib/services/questions'; + +// 批量生成问题 +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + // 获取请求体 + const { model, chunkIds, language = '中文', enableGaExpansion = false } = await request.json(); + + if (!model) { + return NextResponse.json({ error: 'The model cannot be empty' }, { status: 400 }); + } + + // 如果没有指定文本块ID,则获取所有文本块 + let chunks = []; + if (!chunkIds || chunkIds.length === 0) { + const result = await getProjectChunks(projectId); + chunks = result.chunks || []; + } else { + // 获取指定的文本块 + chunks = await Promise.all( + chunkIds.map(async chunkId => { + const chunk = await getChunkById(chunkId); + if (chunk) { + return { + id: chunk.id, + content: chunk.content, + length: chunk.content.length + }; + } + return null; + }) + ); + chunks = chunks.filter(Boolean); // 过滤掉不存在的文本块 + } + if (chunks.length === 0) { + return NextResponse.json({ error: 'No valid text blocks found' }, { status: 404 }); + } + + const results = []; + const errors = []; + + // 获取项目 task-config 信息 + const taskConfig = await getTaskConfig(projectId); + const { questionGenerationLength } = taskConfig; + for (const chunk of chunks) { + try { + // 根据文本长度自动计算问题数量 + const questionNumber = Math.floor(chunk.length / questionGenerationLength); + + let result; + if (enableGaExpansion) { + // 使用GA增强的问题生成 + result = await generateQuestionsForChunkWithGA(projectId, chunk.id, { + model, + language, + number: questionNumber + }); + } else { + // 使用标准问题生成 + result = await generateQuestionsForChunk(projectId, chunk.id, { + model, + language, + number: questionNumber + }); + } + + // 统一处理返回结果格式 + if (result && result.questions && Array.isArray(result.questions)) { + // GA增强模式的结果格式 + results.push({ + chunkId: chunk.id, + success: true, + questions: result.questions, + total: result.total, + gaExpansionUsed: result.gaExpansionUsed, + gaPairsCount: result.gaPairsCount + }); + } else if (result && result.labelQuestions && Array.isArray(result.labelQuestions)) { + // 标准模式的结果格式 + results.push({ + chunkId: chunk.id, + success: true, + questions: result.labelQuestions, + total: result.total, + gaExpansionUsed: false, + gaPairsCount: 0 + }); + } else { + errors.push({ + chunkId: chunk.id, + error: 'Failed to parse questions' + }); + } + } catch (error) { + console.error(`Failed to generate questions for text block ${chunk.id}:`, String(error)); + errors.push({ + chunkId: chunk.id, + error: error.message || 'Failed to generate questions' + }); + } + } + + // 返回生成结果 + return NextResponse.json({ + results, + errors, + totalSuccess: results.length, + totalErrors: errors.length, + totalChunks: chunks.length + }); + } catch (error) { + console.error('Failed to generate questions:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to generate questions' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/huggingface/upload/route.js b/easy-dataset-main/app/api/projects/[projectId]/huggingface/upload/route.js new file mode 100644 index 0000000..5e4de6c --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/huggingface/upload/route.js @@ -0,0 +1,310 @@ +import { NextResponse } from 'next/server'; +import { getProject } from '@/lib/db/projects'; +import { getDatasets } from '@/lib/db/datasets'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import { uploadFiles, createRepo, checkRepoAccess } from '@huggingface/hub'; + +// 上传数据集到 HuggingFace +export async function POST(request, { params }) { + try { + const projectId = params.projectId; + const { + token, + datasetName, + isPrivate, + formatType, + systemPrompt, + confirmedOnly, + includeCOT, + fileFormat, + customFields, + reasoningLanguage + } = await request.json(); + + // 获取项目信息 + const project = await getProject(projectId); + if (!project) { + return NextResponse.json({ error: '项目不存在' }, { status: 404 }); + } + + // 获取数据集问题 + const questions = await getDatasets(projectId, confirmedOnly); + if (!questions || questions.length === 0) { + return NextResponse.json({ error: '没有可用的数据集问题' }, { status: 400 }); + } + + // 格式化数据集 + const formattedData = formatDataset(questions, formatType, systemPrompt, includeCOT, customFields); + + // 创建临时目录 + const tempDir = path.join(os.tmpdir(), `hf-upload-${projectId}-${Date.now()}`); + fs.mkdirSync(tempDir, { recursive: true }); + + // 创建数据集文件 + const datasetFilePath = path.join(tempDir, `dataset.${fileFormat}`); + if (fileFormat === 'json') { + fs.writeFileSync(datasetFilePath, JSON.stringify(formattedData, null, 2)); + } else if (fileFormat === 'jsonl') { + const jsonlContent = formattedData.map(item => JSON.stringify(item)).join('\n'); + fs.writeFileSync(datasetFilePath, jsonlContent); + } else if (fileFormat === 'csv') { + const csvContent = convertToCSV(formattedData); + fs.writeFileSync(datasetFilePath, csvContent); + } + + // 创建 README.md 文件 + const readmePath = path.join(tempDir, 'README.md'); + const readmeContent = generateReadme(project.name, project.description, formatType); + fs.writeFileSync(readmePath, readmeContent); + + // 使用 Hugging Face REST API 上传数据集 + const visibility = isPrivate ? 'private' : 'public'; + + try { + // 准备仓库配置 + const repo = { type: 'dataset', name: datasetName }; + + // 检查仓库是否存在 + let repoExists = true; + try { + await checkRepoAccess({ repo, accessToken: token }); + console.log(`Repository ${datasetName} exists, continuing to upload files`); + } catch (error) { + // If error code is 404, the repository does not exist + if (error.statusCode === 404) { + repoExists = false; + console.log(`Repository ${datasetName} does not exist, preparing to create`); + } else { + // Other errors (e.g., permission errors) + throw new Error(`Failed to check repository access: ${error.message}`); + } + } + + // If the repository does not exist, create a new one + if (!repoExists) { + try { + await createRepo({ + repo, + accessToken: token, + private: isPrivate, + license: 'mit', + description: project.description || 'Dataset created with Easy Dataset' + }); + console.log(`Successfully created dataset repository: ${datasetName}`); + } catch (error) { + throw new Error(`Failed to create dataset repository: ${error.message}`); + } + } + + // 2. 上传数据集文件 + await uploadFile(token, datasetName, datasetFilePath, `dataset.${fileFormat}`); + + // 3. 上传 README.md + await uploadFile(token, datasetName, readmePath, 'README.md'); + } catch (error) { + console.error('Upload to HuggingFace Failed:', String(error)); + return NextResponse.json({ error: `Upload Error: ${error.message}` }, { status: 500 }); + } + + // 清理临时目录 + fs.rmSync(tempDir, { recursive: true, force: true }); + + // 返回成功信息 + const datasetUrl = `https://huggingface.co/datasets/${datasetName}`; + return NextResponse.json({ + success: true, + message: 'Upload successfully HuggingFace', + url: datasetUrl + }); + } catch (error) { + console.error('Upload Faile:', String(error)); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +// 格式化数据集 +function formatDataset(questions, formatType, systemPrompt, includeCOT, customFields) { + if (formatType === 'alpaca') { + return questions.map(q => { + const item = { + instruction: q.question, + input: '', + output: includeCOT && q.cot ? `${q.cot}\n\n${q.answer}` : q.answer + }; + + if (systemPrompt) { + item.system = systemPrompt; + } + + return item; + }); + } else if (formatType === 'sharegpt') { + return questions.map(q => { + const messages = []; + + if (systemPrompt) { + messages.push({ + role: 'system', + content: systemPrompt + }); + } + + messages.push({ + role: 'user', + content: q.question + }); + + messages.push({ + role: 'assistant', + content: includeCOT && q.cot ? `${q.cot}\n\n${q.answer}` : q.answer + }); + + return { messages }; + }); + } else if (formatType === 'multilingualthinking') { + return questions.map(q => { + const messages = []; + + // Main message block + const mainMsg = { + reasoning_language: reasoningLanguage ? reasoningLanguage : 'English', + user: q.question, + analysis: includeCOT && q.cot ? `${q.cot}` : null, + final: q.answer + }; + if (systemPrompt) { + mainMsg.developer = systemPrompt; + } + messages.push(mainMsg); + + // Optional system prompt + if (systemPrompt) { + messages.push({ + role: 'system', + content: systemPrompt, + thinking: null + }); + } + + // User message + messages.push({ + role: 'user', + content: q.question, + thinking: null + }); + + // Assistant message + messages.push({ + role: 'assistant', + content: q.answer, + thinking: includeCOT && q.cot ? `${q.cot}` : null + }); + + return { messages }; + }); + } else if (formatType === 'custom' && customFields) { + return questions.map(q => { + const item = { + [customFields.questionField]: q.question, + [customFields.answerField]: q.answer + }; + + if (includeCOT && q.cot) { + item[customFields.cotField] = q.cot; + } + + if (customFields.includeLabels && q.labels) { + item.labels = q.labels; + } + + if (customFields.includeChunk && q.chunkId) { + item.chunkId = q.chunkId; + } + + return item; + }); + } + + // 默认返回 alpaca 格式 + return questions.map(q => ({ + instruction: q.question, + output: includeCOT && q.cot ? `${q.cot}\n\n${q.answer}` : q.answer + })); +} + +// 将数据转换为 CSV 格式 +function convertToCSV(data) { + if (!data || data.length === 0) return ''; + + const headers = Object.keys(data[0]); + const headerRow = headers.join(','); + + const rows = data.map(item => { + return headers + .map(header => { + const value = item[header]; + if (typeof value === 'string') { + // 处理字符串中的逗号和引号 + return `"${value.replace(/"/g, '""')}"`; + } else if (Array.isArray(value)) { + return `"${JSON.stringify(value).replace(/"/g, '""')}"`; + } else if (typeof value === 'object' && value !== null) { + return `"${JSON.stringify(value).replace(/"/g, '""')}"`; + } + return value; + }) + .join(','); + }); + + return [headerRow, ...rows].join('\n'); +} + +// 使用 @huggingface/hub 包上传文件到 HuggingFace +async function uploadFile(token, datasetName, filePath, destFileName) { + try { + // 准备仓库配置 + const repo = { type: 'dataset', name: datasetName }; + + // 创建文件 URL + const fileUrl = new URL(`file://${filePath}`); + + // 使用 @huggingface/hub 包上传文件 + await uploadFiles({ + repo, + accessToken: token, + files: [ + { + path: destFileName, + content: fileUrl + } + ], + commitTitle: `Upload ${destFileName}`, + commitDescription: `Files uploaded using Easy Dataset` + }); + + return { success: true }; + } catch (error) { + console.error(`File ${destFileName} Upload Error:`, String(error)); + throw error; + } +} + +// Generate README.md file +function generateReadme(projectName, projectDescription, formatType) { + return `# ${projectName} + +## Description +${projectDescription || 'This dataset was created using the Easy Dataset tool.'} + +## Format +This dataset is in ${formatType} format. + +## Creation Method +This dataset was created using the [Easy Dataset](https://github.com/ConardLi/easy-dataset) tool. + +> Easy Dataset is a specialized application designed to streamline the creation of fine-tuning datasets for Large Language Models (LLMs). It offers an intuitive interface for uploading domain-specific files, intelligently splitting content, generating questions, and producing high-quality training data for model fine-tuning. + +`; +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/image-datasets/[datasetId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/[datasetId]/route.js new file mode 100644 index 0000000..7e7a9ea --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/[datasetId]/route.js @@ -0,0 +1,109 @@ +import { NextResponse } from 'next/server'; +import { getImageDatasetById, updateImageDataset, deleteImageDataset } from '@/lib/db/imageDatasets'; +import { getProjectPath } from '@/lib/db/base'; +import fs from 'fs/promises'; +import path from 'path'; + +// 获取单个数据集详情 +export async function GET(request, { params }) { + try { + const { projectId, datasetId } = params; + + const dataset = await getImageDatasetById(datasetId); + + if (!dataset || dataset.projectId !== projectId) { + return NextResponse.json({ error: 'Dataset not found' }, { status: 404 }); + } + + // 获取项目路径 + const projectPath = await getProjectPath(projectId); + + // 读取图片 base64 + let base64 = null; + try { + const imagePath = path.join(projectPath, 'images', dataset.imageName); + const imageBuffer = await fs.readFile(imagePath); + const base64Data = imageBuffer.toString('base64'); + const ext = path.extname(dataset.imageName).toLowerCase(); + const mimeType = ext === '.png' ? 'image/png' : ext === '.gif' ? 'image/gif' : 'image/jpeg'; + base64 = `data:${mimeType};base64,${base64Data}`; + } catch (error) { + console.error(`Failed to read image ${dataset.imageName}:`, error); + } + + // 添加图片 base64 + const datasetWithImage = { + ...dataset, + base64 + }; + + return NextResponse.json(datasetWithImage); + } catch (error) { + console.error('Failed to get dataset detail:', error); + return NextResponse.json({ error: error.message || 'Failed to get dataset detail' }, { status: 500 }); + } +} + +// 更新数据集 +export async function PUT(request, { params }) { + try { + const { projectId, datasetId } = params; + const updates = await request.json(); + + // 验证数据集存在且属于该项目 + const dataset = await getImageDatasetById(datasetId); + if (!dataset || dataset.projectId !== projectId) { + return NextResponse.json({ error: 'Dataset not found' }, { status: 404 }); + } + + // 更新数据集 + const updated = await updateImageDataset(datasetId, updates); + + // 获取项目路径 + const projectPath = await getProjectPath(projectId); + + // 读取图片 base64 + let base64 = null; + try { + const imagePath = path.join(projectPath, 'images', updated.imageName); + const imageBuffer = await fs.readFile(imagePath); + const base64Data = imageBuffer.toString('base64'); + const ext = path.extname(updated.imageName).toLowerCase(); + const mimeType = ext === '.png' ? 'image/png' : ext === '.gif' ? 'image/gif' : 'image/jpeg'; + base64 = `data:${mimeType};base64,${base64Data}`; + } catch (error) { + console.error(`Failed to read image ${updated.imageName}:`, error); + } + + // 添加图片 base64 + const updatedWithImage = { + ...updated, + base64 + }; + + return NextResponse.json(updatedWithImage); + } catch (error) { + console.error('Failed to update dataset:', error); + return NextResponse.json({ error: error.message || 'Failed to update dataset' }, { status: 500 }); + } +} + +// 删除数据集 +export async function DELETE(request, { params }) { + try { + const { projectId, datasetId } = params; + + // 验证数据集存在且属于该项目 + const dataset = await getImageDatasetById(datasetId); + if (!dataset || dataset.projectId !== projectId) { + return NextResponse.json({ error: 'Dataset not found' }, { status: 404 }); + } + + await deleteImageDataset(datasetId); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Failed to delete dataset:', error); + return NextResponse.json({ error: error.message || 'Failed to delete dataset' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/image-datasets/export-zip/route.js b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/export-zip/route.js new file mode 100644 index 0000000..c1c96f4 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/export-zip/route.js @@ -0,0 +1,85 @@ +import { NextResponse } from 'next/server'; +import { getImageDatasetsForExport } from '@/lib/db/imageDatasets'; +import archiver from 'archiver'; +import { getProjectPath } from '@/lib/db/base'; +import path from 'path'; +import fs from 'fs'; + +/** + * 导出图片文件压缩包 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const confirmedOnly = searchParams.get('confirmedOnly') === 'true'; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + + // 获取数据集(用于确定需要哪些图片) + const datasets = await getImageDatasetsForExport(projectId, confirmedOnly); + + if (!datasets || datasets.length === 0) { + return NextResponse.json({ error: 'No data to export' }, { status: 404 }); + } + + // 获取所有需要的图片名称 + const imageNames = new Set(datasets.map(d => d.imageName).filter(Boolean)); + + if (imageNames.size === 0) { + return NextResponse.json({ error: 'No images to export' }, { status: 404 }); + } + + // 创建压缩包 + const archive = archiver('zip', { + zlib: { level: 9 } + }); + + // 设置响应头 + const dateStr = new Date().toISOString().slice(0, 10); + const filename = `images-${projectId}-${dateStr}.zip`; + + // 添加图片文件到压缩包 + const projectPath = await getProjectPath(projectId); + const imageDir = path.join(projectPath, 'images'); + + if (!fs.existsSync(imageDir)) { + return NextResponse.json({ error: 'Image directory not found' }, { status: 404 }); + } + + let addedCount = 0; + for (const imageName of imageNames) { + const imagePath = path.join(imageDir, imageName); + if (fs.existsSync(imagePath)) { + archive.file(imagePath, { name: imageName }); + addedCount++; + } + } + + if (addedCount === 0) { + return NextResponse.json({ error: 'No image files found' }, { status: 404 }); + } + + // 完成压缩 + archive.finalize(); + + // 返回流式响应 + return new NextResponse(archive, { + headers: { + 'Content-Type': 'application/zip', + 'Content-Disposition': `attachment; filename="${filename}"` + } + }); + } catch (error) { + console.error('Failed to export images:', String(error)); + return NextResponse.json( + { + error: error.message || 'Failed to export images' + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/image-datasets/export/route.js b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/export/route.js new file mode 100644 index 0000000..a6f73ca --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/export/route.js @@ -0,0 +1,32 @@ +import { NextResponse } from 'next/server'; +import { getImageDatasetsForExport } from '@/lib/db/imageDatasets'; + +/** + * 导出图像数据集 + */ +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + + const confirmedOnly = body.confirmedOnly || false; + + // 获取数据集 + const datasets = await getImageDatasetsForExport(projectId, confirmedOnly); + + return NextResponse.json(datasets); + } catch (error) { + console.error('Failed to export image datasets:', String(error)); + return NextResponse.json( + { + error: error.message || 'Failed to export image datasets' + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/image-datasets/route.js b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/route.js new file mode 100644 index 0000000..63102dd --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/route.js @@ -0,0 +1,72 @@ +import { NextResponse } from 'next/server'; +import { getImageDatasetsByProject } from '@/lib/db/imageDatasets'; +import { getProjectPath } from '@/lib/db/base'; +import fs from 'fs/promises'; +import path from 'path'; + +// 获取图片数据集列表 +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + const page = parseInt(searchParams.get('page')) || 1; + const pageSize = parseInt(searchParams.get('pageSize')) || 20; + const search = searchParams.get('search') || ''; + const confirmed = searchParams.get('confirmed'); + const minScore = searchParams.get('minScore'); + const maxScore = searchParams.get('maxScore'); + + // 构建筛选条件 + const filters = {}; + if (search) { + filters.search = search; + } + if (confirmed !== null && confirmed !== undefined) { + filters.confirmed = confirmed === 'true'; + } + if (minScore) { + filters.minScore = parseInt(minScore); + } + if (maxScore) { + filters.maxScore = parseInt(maxScore); + } + + const result = await getImageDatasetsByProject(projectId, page, pageSize, filters); + + // 获取项目路径 + const projectPath = await getProjectPath(projectId); + + // 为每个数据集添加图片 base64 + const datasetsWithImages = await Promise.all( + result.data.map(async dataset => { + try { + const imagePath = path.join(projectPath, 'images', dataset.imageName); + const imageBuffer = await fs.readFile(imagePath); + const base64 = imageBuffer.toString('base64'); + const ext = path.extname(dataset.imageName).toLowerCase(); + const mimeType = ext === '.png' ? 'image/png' : ext === '.gif' ? 'image/gif' : 'image/jpeg'; + + return { + ...dataset, + base64: `data:${mimeType};base64,${base64}` + }; + } catch (error) { + console.error(`Failed to read image ${dataset.imageName}:`, error); + return { + ...dataset, + base64: null + }; + } + }) + ); + + return NextResponse.json({ + data: datasetsWithImages, + total: result.total + }); + } catch (error) { + console.error('Failed to get image datasets:', error); + return NextResponse.json({ error: error.message || 'Failed to get image datasets' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/image-datasets/tags/route.js b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/tags/route.js new file mode 100644 index 0000000..8b7d768 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/image-datasets/tags/route.js @@ -0,0 +1,37 @@ +import { NextResponse } from 'next/server'; +import { getImageDatasetsTagsByProject } from '@/lib/db/imageDatasets'; + +// 获取项目中所有已使用的标签 +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 获取项目的所有数据集 + const datasets = await getImageDatasetsTagsByProject(projectId); + + console.log('datasets', datasets); + + // 提取所有标签 + const tagsSet = new Set(); + datasets.forEach(dataset => { + if (dataset.tags) { + try { + const tags = JSON.parse(dataset.tags); + if (Array.isArray(tags)) { + tags.forEach(tag => tagsSet.add(tag)); + } + } catch (e) { + // 忽略解析错误 + } + } + }); + + // 转换为数组并排序 + const tags = Array.from(tagsSet).sort(); + + return NextResponse.json({ tags }); + } catch (error) { + console.error('Failed to get tags:', error); + return NextResponse.json({ error: error.message || 'Failed to get tags' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/[imageId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/[imageId]/route.js new file mode 100644 index 0000000..886a9a5 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/[imageId]/route.js @@ -0,0 +1,31 @@ +import { NextResponse } from 'next/server'; +import { getImageDetailWithQuestions } from '@/lib/services/images'; + +// 根据图片ID获取图片详情,包含问题列表和已标注数据 +export async function GET(request, { params }) { + try { + const { projectId, imageId } = params; + + // 调用服务层获取图片详情 + const imageData = await getImageDetailWithQuestions(projectId, imageId); + + return NextResponse.json({ + success: true, + data: imageData + }); + } catch (error) { + console.error('Failed to get image details:', error); + + // 根据错误类型返回不同的状态码 + let statusCode = 500; + if (error.message === '缺少图片ID') { + statusCode = 400; + } else if (error.message === '图片不存在') { + statusCode = 404; + } else if (error.message === '图片不属于指定项目') { + statusCode = 403; + } + + return NextResponse.json({ error: error.message || 'Failed to get image details' }, { status: statusCode }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/annotations/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/annotations/route.js new file mode 100644 index 0000000..c60eac4 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/annotations/route.js @@ -0,0 +1,89 @@ +import { NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; +import { getImageById, getImageChunk } from '@/lib/db/images'; +import { createImageDataset } from '@/lib/db/imageDatasets'; + +const prisma = new PrismaClient(); + +// 创建标注 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { imageId, questionId, question, answerType, answer, note } = await request.json(); + + // 验证必填字段 + if (!imageId || !question || !answerType || answer === undefined || answer === null) { + return NextResponse.json({ error: '缺少必要参数:imageId, question, answerType, answer' }, { status: 400 }); + } + + // 验证图片存在 + const image = await getImageById(imageId); + if (!image || image.projectId !== projectId) { + return NextResponse.json({ error: '图片不存在' }, { status: 404 }); + } + + // 验证答案类型 + if (!['text', 'label', 'custom_format'].includes(answerType)) { + return NextResponse.json({ error: '无效的答案类型' }, { status: 400 }); + } + + // 验证答案内容 + if (answerType === 'text' && typeof answer !== 'string') { + return NextResponse.json({ error: '文本类型答案必须是字符串' }, { status: 400 }); + } + if (answerType === 'label' && !Array.isArray(answer)) { + return NextResponse.json({ error: '标签类型答案必须是数组' }, { status: 400 }); + } + + // 序列化答案 + let answerString = answer; + if (answerType !== 'text' && typeof answerString !== 'string') { + answerString = JSON.stringify(answer, null, 2); + } + + // 1. 获取问题记录(前端传递的 questionId 指向已有的问题) + if (!questionId) { + return NextResponse.json({ error: '缺少必要参数:questionId' }, { status: 400 }); + } + + const questionRecord = await prisma.questions.findUnique({ + where: { id: questionId } + }); + + if (!questionRecord) { + return NextResponse.json({ error: '问题不存在' }, { status: 404 }); + } + + // 验证问题属于该图片 + if (questionRecord.imageId !== imageId) { + return NextResponse.json({ error: '问题不属于该图片' }, { status: 400 }); + } + + // 2. 更新问题为已回答 + await prisma.questions.update({ + where: { id: questionRecord.id }, + data: { answered: true } + }); + + // 3. 创建 ImageDataset 记录 + const dataset = await createImageDataset(projectId, { + imageId: image.id, + imageName: image.imageName, + questionId: questionRecord.id, + question, + answer: answerString, + answerType, + model: 'manual', + note: note || '' + }); + + return NextResponse.json({ + success: true, + dataset, + questionId: questionRecord.id + }); + } catch (error) { + console.error('Failed to create annotation:', error); + return NextResponse.json({ error: error.message || 'Failed to create annotation' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/datasets/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/datasets/route.js new file mode 100644 index 0000000..82958a2 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/datasets/route.js @@ -0,0 +1,41 @@ +import { NextResponse } from 'next/server'; +import { getImageByName } from '@/lib/db/images'; +import imageService from '@/lib/services/images'; + +// 生成图像数据集 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { imageName, question, model, language = 'zh', previewOnly = false } = await request.json(); + + if (!imageName || !question) { + return NextResponse.json({ error: '缺少必要参数' }, { status: 400 }); + } + + if (!model) { + return NextResponse.json({ error: '请选择一个视觉模型' }, { status: 400 }); + } + + // 获取图片信息 + const image = await getImageByName(projectId, imageName); + if (!image) { + return NextResponse.json({ error: '图片不存在' }, { status: 404 }); + } + + // 调用图片数据集生成服务 + const result = await imageService.generateDatasetForImage(projectId, image.id, question, { + model, + language, + previewOnly + }); + + return NextResponse.json({ + success: true, + answer: result.answer, + dataset: result.dataset + }); + } catch (error) { + console.error('Failed to generate image dataset:', error); + return NextResponse.json({ error: error.message || 'Failed to generate dataset' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/next-unanswered/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/next-unanswered/route.js new file mode 100644 index 0000000..19ad7c6 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/next-unanswered/route.js @@ -0,0 +1,41 @@ +import { NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; +import { getImageDetailWithQuestions } from '@/lib/services/images'; + +const prisma = new PrismaClient(); + +// 获取下一个有未标注问题的图片 +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 查找第一个有未标注问题的图片 + const unansweredQuestion = await prisma.questions.findFirst({ + where: { + projectId, + imageId: { + not: null + }, + answered: false + } + }); + + if (!unansweredQuestion) { + return NextResponse.json({ + success: true, + data: null + }); + } + + // 调用服务层获取图片详情 + const imageData = await getImageDetailWithQuestions(projectId, unansweredQuestion.imageId); + + return NextResponse.json({ + success: true, + data: imageData + }); + } catch (error) { + console.error('Failed to get next unanswered image:', error); + return NextResponse.json({ error: error.message || 'Failed to get next unanswered image' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/pdf-convert/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/pdf-convert/route.js new file mode 100644 index 0000000..b302bad --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/pdf-convert/route.js @@ -0,0 +1,98 @@ +import { NextResponse } from 'next/server'; +import { getProjectPath } from '@/lib/db/base'; +import { importImagesFromDirectories } from '@/lib/services/images'; +import fs from 'fs/promises'; +import path from 'path'; +import { savePdfAsImages } from '@/lib/util/file'; + +// PDF 转图片并导入 +export async function POST(request, { params }) { + let tempPdfPath = null; + let tempImagesDir = null; + + try { + const { projectId } = params; + const formData = await request.formData(); + const pdfFile = formData.get('file'); + + if (!pdfFile) { + return NextResponse.json({ error: '请选择 PDF 文件' }, { status: 400 }); + } + + if (!pdfFile.name.toLowerCase().endsWith('.pdf')) { + return NextResponse.json({ error: '只支持 PDF 文件' }, { status: 400 }); + } + + const projectPath = await getProjectPath(projectId); + const tempDir = path.join(projectPath, 'temp'); + await fs.mkdir(tempDir, { recursive: true }); + + // 1. 保存 PDF 到临时目录 + tempPdfPath = path.join(tempDir, `temp_${Date.now()}_${pdfFile.name}`); + const pdfBuffer = Buffer.from(await pdfFile.arrayBuffer()); + await fs.writeFile(tempPdfPath, pdfBuffer); + + // 2. 创建临时图片目录 + tempImagesDir = path.join(tempDir, `pdf_images_${Date.now()}`); + await fs.mkdir(tempImagesDir, { recursive: true }); + + // 3. 调用 pdf2md-js 转换 PDF 为图片 + console.log('开始转换 PDF 为图片...'); + const imagePaths = await savePdfAsImages(tempPdfPath, tempImagesDir, 3); + console.log('PDF 转换完成,生成图片数量:', imagePaths.length); + + if (!imagePaths || imagePaths.length === 0) { + throw new Error('PDF 转换失败,未生成图片'); + } + + // 4. 直接调用服务层导入图片 + const importResult = await importImagesFromDirectories(projectId, [tempImagesDir]); + + // 5. 清理临时文件 + try { + if (tempPdfPath) { + await fs.unlink(tempPdfPath); + } + if (tempImagesDir) { + const tempImages = await fs.readdir(tempImagesDir); + for (const img of tempImages) { + await fs.unlink(path.join(tempImagesDir, img)); + } + await fs.rmdir(tempImagesDir); + } + const tempDirContents = await fs.readdir(tempDir); + if (tempDirContents.length === 0) { + await fs.rmdir(tempDir); + } + } catch (cleanupErr) { + console.warn('清理临时文件失败:', cleanupErr); + } + + return NextResponse.json({ + success: true, + count: importResult.count, + images: importResult.images, + pdfName: pdfFile.name + }); + } catch (error) { + console.error('Failed to convert PDF:', error); + + // 清理临时文件 + try { + if (tempPdfPath) { + await fs.unlink(tempPdfPath).catch(() => {}); + } + if (tempImagesDir) { + const tempImages = await fs.readdir(tempImagesDir).catch(() => []); + for (const img of tempImages) { + await fs.unlink(path.join(tempImagesDir, img)).catch(() => {}); + } + await fs.rmdir(tempImagesDir).catch(() => {}); + } + } catch (cleanupErr) { + console.warn('清理临时文件失败:', cleanupErr); + } + + return NextResponse.json({ error: error.message || 'Failed to convert PDF' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/questions/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/questions/route.js new file mode 100644 index 0000000..747c816 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/questions/route.js @@ -0,0 +1,40 @@ +import { NextResponse } from 'next/server'; +import { getImageByName } from '@/lib/db/images'; +import imageService from '@/lib/services/images'; + +// 生成图片问题 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { imageName, count = 3, model, language = 'zh' } = await request.json(); + + if (!imageName) { + return NextResponse.json({ error: '缺少图片名称' }, { status: 400 }); + } + + if (!model) { + return NextResponse.json({ error: '请选择一个视觉模型' }, { status: 400 }); + } + + // 获取图片信息 + const image = await getImageByName(projectId, imageName); + if (!image) { + return NextResponse.json({ error: '图片不存在' }, { status: 404 }); + } + + // 调用图片问题生成服务 + const result = await imageService.generateQuestionsForImage(projectId, image.id, { + model, + language, + count + }); + + return NextResponse.json({ + success: true, + questions: result.questions + }); + } catch (error) { + console.error('Failed to generate image questions:', error); + return NextResponse.json({ error: error.message || 'Failed to generate questions' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/route.js new file mode 100644 index 0000000..37e303e --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/route.js @@ -0,0 +1,92 @@ +import { NextResponse } from 'next/server'; +import { getImages, deleteImage, getImageDetail } from '@/lib/db/images'; +import { getProjectPath } from '@/lib/db/base'; +import { db } from '@/lib/db/index'; +import { importImagesFromDirectories } from '@/lib/services/images'; +import fs from 'fs/promises'; +import path from 'path'; + +// 获取图片列表 +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + const page = parseInt(searchParams.get('page')) || 1; + const pageSize = parseInt(searchParams.get('pageSize')) || 20; + const imageName = searchParams.get('imageName') || ''; + const hasQuestions = searchParams.get('hasQuestions'); + const hasDatasets = searchParams.get('hasDatasets'); + const simple = searchParams.get('simple'); + + const result = await getImages(projectId, page, pageSize, imageName, hasQuestions, hasDatasets, simple); + + return NextResponse.json(result); + } catch (error) { + console.error('Failed to get images:', error); + return NextResponse.json({ error: error.message || 'Failed to get images' }, { status: 500 }); + } +} + +// 导入图片 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { directories } = await request.json(); + + // 调用服务层处理图片导入 + const result = await importImagesFromDirectories(projectId, directories); + + return NextResponse.json(result); + } catch (error) { + console.error('Failed to import images:', error); + return NextResponse.json({ error: error.message || 'Failed to import images' }, { status: 500 }); + } +} + +// 删除图片 +export async function DELETE(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const imageId = searchParams.get('imageId'); + + if (!imageId) { + return NextResponse.json({ error: '缺少图片ID' }, { status: 400 }); + } + + // 获取图片信息 + const image = await getImageDetail(imageId); + + if (!image) { + return NextResponse.json({ error: '图片不存在' }, { status: 404 }); + } + + // 删除关联的数据集 + await db.imageDatasets.deleteMany({ + where: { imageId } + }); + + // 删除关联的问题 + await db.questions.deleteMany({ + where: { imageId } + }); + + // 删除文件 + const projectPath = await getProjectPath(projectId); + const filePath = path.join(projectPath, 'images', image.imageName); + try { + await fs.unlink(filePath); + } catch (err) { + console.warn('删除文件失败:', err); + } + + // 删除数据库记录 + await deleteImage(imageId); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('Failed to delete image:', error); + return NextResponse.json({ error: error.message || 'Failed to delete image' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/images/zip-import/route.js b/easy-dataset-main/app/api/projects/[projectId]/images/zip-import/route.js new file mode 100644 index 0000000..dd4786d --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/images/zip-import/route.js @@ -0,0 +1,127 @@ +import { NextResponse } from 'next/server'; +import { getProjectPath } from '@/lib/db/base'; +import { importImagesFromDirectories } from '@/lib/services/images'; +import fs from 'fs/promises'; +import path from 'path'; +import AdmZip from 'adm-zip'; + +// 压缩包解压并导入图片 +export async function POST(request, { params }) { + let tempZipPath = null; + let tempExtractDir = null; + + try { + const { projectId } = params; + const formData = await request.formData(); + const zipFile = formData.get('file'); + + if (!zipFile) { + return NextResponse.json({ error: '请选择压缩包文件' }, { status: 400 }); + } + + if (!zipFile.name.toLowerCase().endsWith('.zip')) { + return NextResponse.json({ error: '只支持 ZIP 格式的压缩包' }, { status: 400 }); + } + + const projectPath = await getProjectPath(projectId); + const tempDir = path.join(projectPath, 'temp'); + await fs.mkdir(tempDir, { recursive: true }); + + // 1. 保存压缩包到临时目录 + tempZipPath = path.join(tempDir, `temp_${Date.now()}_${zipFile.name}`); + const zipBuffer = Buffer.from(await zipFile.arrayBuffer()); + await fs.writeFile(tempZipPath, zipBuffer); + + // 2. 创建临时解压目录 + tempExtractDir = path.join(tempDir, `zip_extract_${Date.now()}`); + await fs.mkdir(tempExtractDir, { recursive: true }); + + // 3. 使用 adm-zip 解压文件 + console.log('开始解压压缩包...'); + const zip = new AdmZip(tempZipPath); + const zipEntries = zip.getEntries(); + + // 支持的图片扩展名 + const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg']; + let extractedCount = 0; + + // 遍历压缩包中的所有文件 + for (const entry of zipEntries) { + // 跳过目录和隐藏文件 + if ( + entry.isDirectory || + entry.entryName.startsWith('__MACOSX') || + path.basename(entry.entryName).startsWith('.') + ) { + continue; + } + + const ext = path.extname(entry.entryName).toLowerCase(); + if (imageExtensions.includes(ext)) { + // 提取文件名(不包含路径) + const fileName = path.basename(entry.entryName); + const targetPath = path.join(tempExtractDir, fileName); + + // 解压文件 + zip.extractEntryTo(entry, tempExtractDir, false, true, false, fileName); + extractedCount++; + } + } + + console.log(`压缩包解压完成,提取图片数量: ${extractedCount}`); + + if (extractedCount === 0) { + throw new Error('压缩包中没有找到支持的图片文件'); + } + + // 4. 调用服务层导入图片 + const importResult = await importImagesFromDirectories(projectId, [tempExtractDir]); + + // 5. 清理临时文件 + try { + if (tempZipPath) { + await fs.unlink(tempZipPath); + } + if (tempExtractDir) { + const tempImages = await fs.readdir(tempExtractDir); + for (const img of tempImages) { + await fs.unlink(path.join(tempExtractDir, img)); + } + await fs.rmdir(tempExtractDir); + } + const tempDirContents = await fs.readdir(tempDir); + if (tempDirContents.length === 0) { + await fs.rmdir(tempDir); + } + } catch (cleanupErr) { + console.warn('清理临时文件失败:', cleanupErr); + } + + return NextResponse.json({ + success: true, + count: importResult.count, + images: importResult.images, + zipName: zipFile.name + }); + } catch (error) { + console.error('Failed to import ZIP:', error); + + // 清理临时文件 + try { + if (tempZipPath) { + await fs.unlink(tempZipPath).catch(() => {}); + } + if (tempExtractDir) { + const tempImages = await fs.readdir(tempExtractDir).catch(() => []); + for (const img of tempImages) { + await fs.unlink(path.join(tempExtractDir, img)).catch(() => {}); + } + await fs.rmdir(tempExtractDir).catch(() => {}); + } + } catch (cleanupErr) { + console.warn('清理临时文件失败:', cleanupErr); + } + + return NextResponse.json({ error: error.message || 'Failed to import ZIP' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/llamaFactory/checkConfig/route.js b/easy-dataset-main/app/api/projects/[projectId]/llamaFactory/checkConfig/route.js new file mode 100644 index 0000000..7049298 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/llamaFactory/checkConfig/route.js @@ -0,0 +1,27 @@ +import { NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs'; +import { getProjectRoot } from '@/lib/db/base'; + +export async function GET(request, { params }) { + try { + const { projectId } = params; + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + const configPath = path.join(projectPath, 'dataset_info.json'); + + const exists = fs.existsSync(configPath); + + return NextResponse.json({ + exists, + configPath: exists ? configPath : null + }); + } catch (error) { + console.error('Error checking Llama Factory config:', String(error)); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/llamaFactory/generate/route.js b/easy-dataset-main/app/api/projects/[projectId]/llamaFactory/generate/route.js new file mode 100644 index 0000000..41d7275 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/llamaFactory/generate/route.js @@ -0,0 +1,141 @@ +import { NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs'; +import { getProjectRoot } from '@/lib/db/base'; +import { getDatasets } from '@/lib/db/datasets'; + +export async function POST(request, { params }) { + try { + const { projectId } = params; + const { formatType, systemPrompt, confirmedOnly, includeCOT, reasoningLanguage } = await request.json(); + + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + const configPath = path.join(projectPath, 'dataset_info.json'); + const alpacaPath = path.join(projectPath, 'alpaca.json'); + const sharegptPath = path.join(projectPath, 'sharegpt.json'); + const multilingualThinkingPath = path.join(projectPath, 'multilingual-thinking.json'); + + // 获取数据集 + let datasets = await getDatasets(projectId, !!confirmedOnly); + + // 创建 dataset_info.json 配置 + const config = { + [`[Easy Dataset] [${projectId}] Alpaca`]: { + file_name: 'alpaca.json', + columns: { + prompt: 'instruction', + query: 'input', + response: 'output', + system: 'system' + } + }, + [`[Easy Dataset] [${projectId}] ShareGPT`]: { + file_name: 'sharegpt.json', + formatting: 'sharegpt', + columns: { + messages: 'messages' + }, + tags: { + role_tag: 'role', + content_tag: 'content', + user_tag: 'user', + assistant_tag: 'assistant', + system_tag: 'system' + } + }, + [`[Easy Dataset] [${projectId}] multilingual-thinking`]: { + file_name: 'multilingual-thinking.json', + formatting: 'multilingual-thinking', + columns: { + messages: 'messages' + }, + tags: { + role_tag: 'role', + content_tag: 'content', + user_tag: 'user', + assistant_tag: 'assistant', + system_tag: 'system' + } + } + }; + + // 生成数据文件 + const alpacaData = datasets.map(({ question, answer, cot }) => ({ + instruction: question, + input: '', + output: cot && includeCOT ? `${cot}\n${answer}` : answer, + system: systemPrompt || '' + })); + + const sharegptData = datasets.map(({ question, answer, cot }) => { + const messages = []; + if (systemPrompt) { + messages.push({ + role: 'system', + content: systemPrompt + }); + } + messages.push({ + role: 'user', + content: question + }); + messages.push({ + role: 'assistant', + content: cot && includeCOT ? `${cot}\n${answer}` : answer + }); + return { messages }; + }); + const multilingualThinkingData = datasets.map(({ question, answer, cot }) => ({ + reasoning_language: reasoningLanguage ? reasoningLanguage : 'English', + developer: systemPrompt ? systemPrompt : '', // system prompt (may be empty) + user: question, + analysis: includeCOT && cot ? cot : null, // null if no COT + final: answer, + messages: [ + { + content: systemPrompt ? systemPrompt : '', + role: 'system', + thinking: null + }, + { + content: question, + role: 'user', + thinking: null + }, + { + content: answer, + role: 'assistant', + thinking: includeCOT && cot ? cot : null + } + ] + })); + + const multilingualThinkingLines = multilingualThinkingData.map(item => JSON.stringify(item, null, 2)).join('\n'); + + await fs.promises.writeFile(multilingualThinkingPath, multilingualThinkingLines, 'utf8'); + + // 写入文件 + await fs.promises.writeFile(configPath, JSON.stringify(config, null, 2)); + await fs.promises.writeFile(alpacaPath, JSON.stringify(alpacaData, null, 2)); + await fs.promises.writeFile(sharegptPath, JSON.stringify(sharegptData, null, 2)); + + return NextResponse.json({ + success: true, + configPath, + files: [ + { path: alpacaPath, format: 'alpaca' }, + { path: sharegptPath, format: 'sharegpt' }, + { path: multilingualThinkingPath, format: 'multilingual-thinking' } + ] + }); + } catch (error) { + console.error('Error generating Llama Factory config:', String(error)); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/model-config/[modelConfigId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/model-config/[modelConfigId]/route.js new file mode 100644 index 0000000..35e0d9c --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/model-config/[modelConfigId]/route.js @@ -0,0 +1,18 @@ +import { NextResponse } from 'next/server'; +import { deleteModelConfigById } from '@/lib/db/model-config'; + +// 删除模型配置 +export async function DELETE(request, { params }) { + try { + const { projectId, modelConfigId } = params; + // 验证项目 ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + await deleteModelConfigById(modelConfigId); + return NextResponse.json(true); + } catch (error) { + console.error('Error obtaining model configuration:', String(error)); + return NextResponse.json({ error: 'Failed to obtain model configuration' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/model-config/route.js b/easy-dataset-main/app/api/projects/[projectId]/model-config/route.js new file mode 100644 index 0000000..d954c80 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/model-config/route.js @@ -0,0 +1,103 @@ +import { NextResponse } from 'next/server'; +import { createInitModelConfig, getModelConfigByProjectId, saveModelConfig } from '@/lib/db/model-config'; +import { DEFAULT_MODEL_SETTINGS, MODEL_PROVIDERS } from '@/constant/model'; +import { getProject } from '@/lib/db/projects'; +import { sortProvidersByPriority } from '@/lib/util/providerLogo'; + +function normalizeModelEndpoint(endpoint = '') { + let normalizedEndpoint = String(endpoint).trim(); + if (!normalizedEndpoint) { + return ''; + } + if (normalizedEndpoint.includes('/chat/completions')) { + normalizedEndpoint = normalizedEndpoint.replace('/chat/completions', ''); + } + return normalizedEndpoint; +} + +// 获取模型配置列表 +export async function GET(request, { params }) { + try { + const { projectId } = params; + // 验证项目 ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + let modelConfigList = await getModelConfigByProjectId(projectId); + if (!modelConfigList || modelConfigList.length === 0) { + let insertModelConfigList = []; + const sortedProviders = sortProvidersByPriority(MODEL_PROVIDERS, item => item.id); + sortedProviders.forEach(item => { + let data = { + projectId: projectId, + providerId: item.id, + providerName: item.name, + endpoint: item.defaultEndpoint, + apiKey: '', + modelId: '', + modelName: '', + type: 'text', + temperature: DEFAULT_MODEL_SETTINGS.temperature, + maxTokens: DEFAULT_MODEL_SETTINGS.maxTokens, + topK: 0, + topP: DEFAULT_MODEL_SETTINGS.topP, + status: 1 + }; + insertModelConfigList.push(data); + }); + modelConfigList = await createInitModelConfig(insertModelConfigList); + } + modelConfigList = sortProvidersByPriority(modelConfigList, item => item.providerId); + let project = await getProject(projectId); + return NextResponse.json({ data: modelConfigList, defaultModelConfigId: project.defaultModelConfigId }); + } catch (error) { + console.error('Error obtaining model configuration:', String(error)); + return NextResponse.json({ error: 'Failed to obtain model configuration' }, { status: 500 }); + } +} + +// 保存模型配置 +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // 验证项目 ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + // 获取请求体 + const modelConfig = await request.json(); + + // 验证请求体 + if (!modelConfig) { + return NextResponse.json({ error: 'The model configuration cannot be empty ' }, { status: 400 }); + } + modelConfig.projectId = projectId; + modelConfig.endpoint = normalizeModelEndpoint(modelConfig.endpoint); + // 如果没有 modelId,使用 modelName 补齐(兼容旧逻辑) + if (!modelConfig.modelId && modelConfig.modelName) { + modelConfig.modelId = modelConfig.modelName; + } + // 如果没有 modelName,使用 modelId 补齐 + if (!modelConfig.modelName && modelConfig.modelId) { + modelConfig.modelName = modelConfig.modelId; + } + if (!modelConfig.topK) { + modelConfig.topK = 0; + } + if (!modelConfig.status) { + modelConfig.status = 1; + } + const parsedMaxTokens = Number(modelConfig.maxTokens ?? DEFAULT_MODEL_SETTINGS.maxTokens); + if (!Number.isInteger(parsedMaxTokens) || parsedMaxTokens < 1) { + return NextResponse.json({ error: 'maxTokens must be a positive integer' }, { status: 400 }); + } + modelConfig.maxTokens = parsedMaxTokens; + const res = await saveModelConfig(modelConfig); + + return NextResponse.json(res); + } catch (error) { + console.error('Error updating model configuration:', String(error)); + return NextResponse.json({ error: 'Failed to update model configuration' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/models/[modelId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/models/[modelId]/route.js new file mode 100644 index 0000000..047d953 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/models/[modelId]/route.js @@ -0,0 +1,173 @@ +import { NextResponse } from 'next/server'; +import { getProjectRoot } from '@/lib/db/base'; +import path from 'path'; +import fs from 'fs/promises'; + +export async function GET(request, { params }) { + try { + const { projectId, modelId } = params; + + // 验证项目ID和模型ID + if (!projectId || !modelId) { + return NextResponse.json({ error: 'The project ID and model ID cannot be empty' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + + // 获取模型配置文件路径 + const modelConfigPath = path.join(projectPath, 'model-config.json'); + + // 检查模型配置文件是否存在 + try { + await fs.access(modelConfigPath); + } catch (error) { + return NextResponse.json({ error: 'The model configuration does not exist' }, { status: 404 }); + } + + // 读取模型配置文件 + const modelConfigData = await fs.readFile(modelConfigPath, 'utf-8'); + const modelConfig = JSON.parse(modelConfigData); + + // 查找指定ID的模型 + const model = modelConfig.find(model => model.id === modelId); + + if (!model) { + return NextResponse.json({ error: 'The model does not exist' }, { status: 404 }); + } + + return NextResponse.json(model); + } catch (error) { + console.error('Error getting model:', String(error)); + return NextResponse.json({ error: 'Failed to get model' }, { status: 500 }); + } +} + +export async function PUT(request, { params }) { + try { + const { projectId, modelId } = params; + + // 验证项目ID和模型ID + if (!projectId || !modelId) { + return NextResponse.json({ error: 'The project ID and model ID cannot be empty' }, { status: 400 }); + } + + // 获取请求体 + const modelData = await request.json(); + + // 验证请求体 + if (!modelData || !modelData.provider || !modelData.name) { + return NextResponse.json({ error: 'The model data is incomplete' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + + // 获取模型配置文件路径 + const modelConfigPath = path.join(projectPath, 'model-config.json'); + + // 读取模型配置文件 + let modelConfig = []; + try { + const modelConfigData = await fs.readFile(modelConfigPath, 'utf-8'); + modelConfig = JSON.parse(modelConfigData); + } catch (error) { + // 如果文件不存在,创建一个空数组 + } + + // 更新模型数据 + const modelIndex = modelConfig.findIndex(model => model.id === modelId); + + if (modelIndex >= 0) { + // 更新现有模型 + modelConfig[modelIndex] = { + ...modelConfig[modelIndex], + ...modelData, + id: modelId // 确保ID不变 + }; + } else { + // 添加新模型 + modelConfig.push({ + ...modelData, + id: modelId + }); + } + + // 写入模型配置文件 + await fs.writeFile(modelConfigPath, JSON.stringify(modelConfig, null, 2), 'utf-8'); + + return NextResponse.json({ message: 'Model configuration updated successfully' }); + } catch (error) { + console.error('Error updating model configuration:', String(error)); + return NextResponse.json({ error: 'Failed to update model configuration' }, { status: 500 }); + } +} + +export async function DELETE(request, { params }) { + try { + const { projectId, modelId } = params; + + // 验证项目ID和模型ID + if (!projectId || !modelId) { + return NextResponse.json({ error: 'The project ID and model ID cannot be empty' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + + // 获取模型配置文件路径 + const modelConfigPath = path.join(projectPath, 'model-config.json'); + + // 检查模型配置文件是否存在 + try { + await fs.access(modelConfigPath); + } catch (error) { + return NextResponse.json({ error: 'The model configuration does not exist' }, { status: 404 }); + } + + // 读取模型配置文件 + const modelConfigData = await fs.readFile(modelConfigPath, 'utf-8'); + let modelConfig = JSON.parse(modelConfigData); + + // 过滤掉要删除的模型 + const initialLength = modelConfig.length; + modelConfig = modelConfig.filter(model => model.id !== modelId); + + // 检查是否找到并删除了模型 + if (modelConfig.length === initialLength) { + return NextResponse.json({ error: 'The model does not exist' }, { status: 404 }); + } + + // 写入模型配置文件 + await fs.writeFile(modelConfigPath, JSON.stringify(modelConfig, null, 2), 'utf-8'); + + return NextResponse.json({ message: 'Model deleted successfully' }); + } catch (error) { + console.error('Error deleting model:', String(error)); + return NextResponse.json({ error: 'Failed to delete model' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/models/route.js b/easy-dataset-main/app/api/projects/[projectId]/models/route.js new file mode 100644 index 0000000..94b7590 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/models/route.js @@ -0,0 +1,89 @@ +import { NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs/promises'; +import { getProjectRoot } from '@/lib/db/base'; + +// 获取模型配置 +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 验证项目 ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + + // 获取模型配置文件路径 + const modelConfigPath = path.join(projectPath, 'model-config.json'); + + // 检查模型配置文件是否存在 + try { + await fs.access(modelConfigPath); + } catch (error) { + // 如果配置文件不存在,返回默认配置 + return NextResponse.json([]); + } + + // 读取模型配置文件 + const modelConfigData = await fs.readFile(modelConfigPath, 'utf-8'); + const modelConfig = JSON.parse(modelConfigData); + + return NextResponse.json(modelConfig); + } catch (error) { + console.error('Error obtaining model configuration:', String(error)); + return NextResponse.json({ error: 'Failed to obtain model configuration' }, { status: 500 }); + } +} + +// 更新模型配置 +export async function PUT(request, { params }) { + try { + const { projectId } = params; + + // 验证项目 ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + // 获取请求体 + const modelConfig = await request.json(); + + // 验证请求体 + if (!modelConfig || !Array.isArray(modelConfig)) { + return NextResponse.json({ error: 'The model configuration must be an array' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'The project does not exist' }, { status: 404 }); + } + + // 获取模型配置文件路径 + const modelConfigPath = path.join(projectPath, 'model-config.json'); + + // 写入模型配置文件 + await fs.writeFile(modelConfigPath, JSON.stringify(modelConfig, null, 2), 'utf-8'); + + return NextResponse.json({ message: 'Model configuration updated successfully' }); + } catch (error) { + console.error('Error updating model configuration:', String(error)); + return NextResponse.json({ error: 'Failed to update model configuration' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/playground/chat/route.js b/easy-dataset-main/app/api/projects/[projectId]/playground/chat/route.js new file mode 100644 index 0000000..6e0cdf7 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/playground/chat/route.js @@ -0,0 +1,99 @@ +import { NextResponse } from 'next/server'; +import LLMClient from '@/lib/llm/core/index'; +import { getModelConfigById } from '@/lib/db/model-config'; + +async function resolveLatestModelConfig(projectId, incomingModel = {}) { + const modelId = incomingModel?.id; + if (!modelId) { + return incomingModel; + } + + try { + const latestModelConfig = await getModelConfigById(modelId); + if (!latestModelConfig) { + return incomingModel; + } + if (String(latestModelConfig.projectId) !== String(projectId)) { + return incomingModel; + } + + // Keep transient client-only fields, but force endpoint/auth/model fields to latest DB values. + return { + ...incomingModel, + ...latestModelConfig + }; + } catch (error) { + console.error('Failed to resolve latest model config:', String(error)); + return incomingModel; + } +} + +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // Validate project ID. + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + // Read request payload. + const { model, messages } = await request.json(); + const resolvedModel = await resolveLatestModelConfig(projectId, model); + + // Validate request parameters. + if (!resolvedModel) { + return NextResponse.json({ error: 'The model parameters cannot be empty' }, { status: 400 }); + } + + if (!Array.isArray(messages) || messages.length === 0) { + return NextResponse.json({ error: 'The message list cannot be empty' }, { status: 400 }); + } + + // Use custom LLM client. + const llmClient = new LLMClient(resolvedModel); + + // Normalize message payload for text + vision models. + const formattedMessages = messages.map(msg => { + // Plain text message. + if (typeof msg.content === 'string') { + return { + role: msg.role, + content: msg.content + }; + } + // Multimodal message (e.g. image parts). + if (Array.isArray(msg.content)) { + return { + role: msg.role, + content: msg.content + }; + } + // Fallback. + return { + role: msg.role, + content: msg.content + }; + }); + + // Call LLM API. + let response = ''; + try { + const { answer, cot } = await llmClient.getResponseWithCOT(formattedMessages.filter(f => f.role !== 'error')); + response = `${cot}${answer}`; + } catch (error) { + console.error('Failed to call LLM API:', String(error)); + return NextResponse.json( + { + error: `Failed to call ${resolvedModel.modelId || resolvedModel.modelName || 'unknown'} model: ${error.message}` + }, + { status: 500 } + ); + } + + return NextResponse.json({ response }); + } catch (error) { + console.error('Failed to process chat request:', String(error)); + return NextResponse.json({ error: `Failed to process chat request: ${error.message}` }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/playground/chat/stream/route.js b/easy-dataset-main/app/api/projects/[projectId]/playground/chat/stream/route.js new file mode 100644 index 0000000..30c2308 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/playground/chat/stream/route.js @@ -0,0 +1,89 @@ +import { NextResponse } from 'next/server'; +import LLMClient from '@/lib/llm/core/index'; +import { getModelConfigById } from '@/lib/db/model-config'; + +async function resolveLatestModelConfig(projectId, incomingModel = {}) { + const modelId = incomingModel?.id; + if (!modelId) { + return incomingModel; + } + + try { + const latestModelConfig = await getModelConfigById(modelId); + if (!latestModelConfig) { + return incomingModel; + } + if (String(latestModelConfig.projectId) !== String(projectId)) { + return incomingModel; + } + + return { + ...incomingModel, + ...latestModelConfig + }; + } catch (error) { + console.error('Failed to resolve latest model config:', String(error)); + return incomingModel; + } +} + +/** + * Streaming chat endpoint. + */ +export async function POST(request, { params }) { + const { projectId } = params; + + try { + const body = await request.json(); + const { model, messages } = body; + const resolvedModel = await resolveLatestModelConfig(projectId, model); + + if (!resolvedModel || !messages) { + return NextResponse.json({ error: 'Missing necessary parameters' }, { status: 400 }); + } + + // Use custom LLM client. + const llmClient = new LLMClient(resolvedModel); + + // Normalize message payload for text + vision models. + const formattedMessages = messages.map(msg => { + // Plain text message. + if (typeof msg.content === 'string') { + return { + role: msg.role, + content: msg.content + }; + } + // Multimodal message (e.g. image parts). + if (Array.isArray(msg.content)) { + return { + role: msg.role, + content: msg.content + }; + } + // Fallback. + return { + role: msg.role, + content: msg.content + }; + }); + + try { + // Stream response from provider. + const response = await llmClient.chatStreamAPI(formattedMessages.filter(f => f.role !== 'error')); + // Return native streaming response. + return response; + } catch (error) { + console.error('Failed to call LLM API:', error); + return NextResponse.json( + { + error: `Failed to call ${resolvedModel.modelId || resolvedModel.modelName || 'unknown'} model: ${error.message}` + }, + { status: 500 } + ); + } + } catch (error) { + console.error('Failed to process stream chat request:', String(error)); + return NextResponse.json({ error: `Failed to process stream chat request: ${error.message}` }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/preview/[fileId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/preview/[fileId]/route.js new file mode 100644 index 0000000..c666dc5 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/preview/[fileId]/route.js @@ -0,0 +1,42 @@ +import { NextResponse } from 'next/server'; +import fs from 'fs'; +import path from 'path'; +import { getProjectRoot } from '@/lib/db/base'; +import { getUploadFileInfoById } from '@/lib/db/upload-files'; + +// 获取文件内容 +export async function GET(request, { params }) { + try { + const { projectId, fileId } = params; + + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: 'Project ID cannot be empty' }, { status: 400 }); + } + + // 获取项目根目录 + let fileInfo = await getUploadFileInfoById(fileId); + if (!fileInfo) { + return NextResponse.json({ error: 'file does not exist' }, { status: 400 }); + } + + // 获取文件路径 + let filePath = path.join(fileInfo.path, fileInfo.fileName); + if (fileInfo.fileExt !== '.md') { + filePath = path.join(fileInfo.path, fileInfo.fileName.replace(/\.[^/.]+$/, '.md')); + } + //获取文件 + const buffer = fs.readFileSync(filePath); + + const text = buffer.toString('utf-8'); + + return NextResponse.json({ + fileId: fileId, + fileName: fileInfo.fileName, + content: text + }); + } catch (error) { + console.error('Failed to get text block content:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to get text block content' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/questions/[questionId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/questions/[questionId]/route.js new file mode 100644 index 0000000..a8285ca --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/questions/[questionId]/route.js @@ -0,0 +1,26 @@ +import { NextResponse } from 'next/server'; +import { deleteQuestion } from '@/lib/db/questions'; + +// 删除单个问题 +export async function DELETE(request, { params }) { + try { + const { projectId, questionId } = params; + + // 验证参数 + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + if (!questionId) { + return NextResponse.json({ error: 'Question ID is required' }, { status: 400 }); + } + + // 删除问题 + await deleteQuestion(questionId); + + return NextResponse.json({ success: true, message: 'Delete successful' }); + } catch (error) { + console.error('Delete failed:', String(error)); + return NextResponse.json({ error: error.message || 'Delete failed' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/questions/batch-delete/route.js b/easy-dataset-main/app/api/projects/[projectId]/questions/batch-delete/route.js new file mode 100644 index 0000000..dcc33ef --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/questions/batch-delete/route.js @@ -0,0 +1,23 @@ +import { NextResponse } from 'next/server'; +import { batchDeleteQuestions } from '@/lib/db/questions'; + +// 批量删除问题 +export async function DELETE(request) { + try { + const body = await request.json(); + const { questionIds } = body; + + // 验证参数 + if (questionIds.length === 0) { + return NextResponse.json({ error: 'Question ID is required' }, { status: 400 }); + } + + // 删除问题 + await batchDeleteQuestions(questionIds); + + return NextResponse.json({ success: true, message: 'Delete successful' }); + } catch (error) { + console.error('Delete failed:', String(error)); + return NextResponse.json({ error: error.message || 'Delete failed' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/questions/export/route.js b/easy-dataset-main/app/api/projects/[projectId]/questions/export/route.js new file mode 100644 index 0000000..c1f002f --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/questions/export/route.js @@ -0,0 +1,100 @@ +import { NextResponse } from 'next/server'; + +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + const { format, selectedIds, filters } = body; + + let questions; + + // 如果有选中的问题 ID,按 ID 获取 + if (selectedIds && selectedIds.length > 0) { + questions = await getQuestionsByIds(projectId, selectedIds); + } else { + // 否则获取全部问题(不限分页) + questions = await getAllQuestions( + projectId, + filters?.searchTerm || '', + filters?.chunkName || '', + filters?.sourceType || 'all' + ); + } + + // 固定导出字段:问题内容、文本块名称、问题标签 + const filteredQuestions = questions.map(q => ({ + question: q.question, + chunkName: q.chunk?.name || q.chunkName || '', + questionLabel: q.questionLabel || '' + })); + + return NextResponse.json(filteredQuestions); + } catch (error) { + console.error('Failed to export questions:', error); + return NextResponse.json({ error: error.message }, { status: 500 }); + } +} + +// 获取全部问题(不限分页) +async function getAllQuestions(projectId, searchTerm = '', chunkName = '', sourceType = 'all') { + const { db } = await import('@/lib/db/index'); + + const whereClause = { + projectId + }; + + // 搜索条件 + if (searchTerm) { + whereClause.OR = [{ question: { contains: searchTerm } }, { questionLabel: { contains: searchTerm } }]; + } + + // 文本块名称筛选 + if (chunkName) { + whereClause.chunk = { + name: { contains: chunkName } + }; + } + + // 数据源类型筛选 + if (sourceType === 'text') { + whereClause.imageName = null; + } else if (sourceType === 'image') { + whereClause.imageName = { not: null }; + } + + return await db.questions.findMany({ + where: whereClause, + include: { + chunk: { + select: { + name: true + } + } + }, + orderBy: { + createAt: 'desc' + } + }); +} + +// 根据 ID 列表获取问题 +async function getQuestionsByIds(projectId, questionIds) { + const { db } = await import('@/lib/db/index'); + + return await db.questions.findMany({ + where: { + projectId, + id: { in: questionIds } + }, + include: { + chunk: { + select: { + name: true + } + } + }, + orderBy: { + createAt: 'desc' + } + }); +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/questions/route.js b/easy-dataset-main/app/api/projects/[projectId]/questions/route.js new file mode 100644 index 0000000..fd7810c --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/questions/route.js @@ -0,0 +1,110 @@ +import { NextResponse } from 'next/server'; +import { + getAllQuestionsByProjectId, + getQuestions, + getQuestionsIds, + saveQuestions, + updateQuestion +} from '@/lib/db/questions'; +import { getImageById, getImageChunk } from '@/lib/db/images'; + +// 获取项目的所有问题 +export async function GET(request, { params }) { + try { + const { projectId } = params; + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Missing project ID' }, { status: 400 }); + } + const { searchParams } = new URL(request.url); + let status = searchParams.get('status'); + let answered = undefined; + if (status === 'answered') answered = true; + if (status === 'unanswered') answered = false; + const chunkName = searchParams.get('chunkName'); + const sourceType = searchParams.get('sourceType') || 'all'; // 'all', 'text', 'image' + const searchMatchMode = searchParams.get('searchMatchMode') || 'match'; // 'match', 'notMatch' + let selectedAll = searchParams.get('selectedAll'); + if (selectedAll) { + let data = await getQuestionsIds( + projectId, + answered, + searchParams.get('input'), + chunkName, + sourceType, + searchMatchMode + ); + return NextResponse.json(data); + } + let all = searchParams.get('all'); + if (all) { + let data = await getAllQuestionsByProjectId(projectId); + return NextResponse.json(data); + } + // 获取问题列表 + const questions = await getQuestions( + projectId, + parseInt(searchParams.get('page')), + parseInt(searchParams.get('size')), + answered, + searchParams.get('input'), + chunkName, + sourceType, + searchMatchMode + ); + + return NextResponse.json(questions); + } catch (error) { + console.error('Failed to get questions:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to get questions' }, { status: 500 }); + } +} + +// 新增问题 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const body = await request.json(); + const { question, chunkId, label } = body; + + // 验证必要参数 + if (!projectId || !question) { + return NextResponse.json({ error: 'Missing necessary parameters' }, { status: 400 }); + } + + if (!body.chunkId && body.imageId) { + const chunk = await getImageChunk(projectId); + body.chunkId = chunk.id; + body.label = 'image'; + } + + // 添加新问题 + let questions = [body]; + // 保存更新后的数据 + let data = await saveQuestions(projectId, questions); + + // 返回成功响应 + return NextResponse.json(data); + } catch (error) { + console.error('Failed to create question:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to create question' }, { status: 500 }); + } +} + +// 更新问题 +export async function PUT(request) { + try { + const body = await request.json(); + // 保存更新后的数据 + const { imageId } = body; + if (imageId) { + body.imageName = (await getImageById(imageId))?.imageName; + } + let data = await updateQuestion(body); + // 返回更新后的问题数据 + return NextResponse.json(data); + } catch (error) { + console.error('更新问题失败:', String(error)); + return NextResponse.json({ error: error.message || '更新问题失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/questions/templates/[templateId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/questions/templates/[templateId]/route.js new file mode 100644 index 0000000..556d427 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/questions/templates/[templateId]/route.js @@ -0,0 +1,110 @@ +import { NextResponse } from 'next/server'; +import templateDb from '@/lib/db/questionTemplates'; +import { generateQuestionsFromTemplateEdit } from '@/lib/services/questions/template'; + +// 获取单个模板 +export async function GET(request, { params }) { + try { + const { templateId } = params; + + const template = await templateDb.getTemplateById(templateId); + + if (!template) { + return NextResponse.json({ error: '模板不存在' }, { status: 404 }); + } + + // 获取使用统计 + const usageCount = await templateDb.getTemplateUsageCount(templateId); + + return NextResponse.json({ + success: true, + template: { + ...template, + usageCount + } + }); + } catch (error) { + console.error('Failed to get template:', error); + return NextResponse.json({ error: error.message || 'Failed to get template' }, { status: 500 }); + } +} + +// 更新问题模板 +export async function PUT(request, { params }) { + try { + const { projectId, templateId } = params; + const data = await request.json(); + + const { question, sourceType, answerType, description, labels, customFormat, order, autoGenerate } = data; + + // 验证数据源类型 + if (sourceType && !['image', 'text'].includes(sourceType)) { + return NextResponse.json({ error: '无效的数据源类型' }, { status: 400 }); + } + + // 验证答案类型 + if (answerType && !['text', 'label', 'custom_format'].includes(answerType)) { + return NextResponse.json({ error: '无效的答案类型' }, { status: 400 }); + } + + const updateData = {}; + if (question !== undefined) updateData.question = question; + if (sourceType !== undefined) updateData.sourceType = sourceType; + if (answerType !== undefined) updateData.answerType = answerType; + if (description !== undefined) updateData.description = description; + if (labels !== undefined) updateData.labels = labels; + if (customFormat !== undefined) updateData.customFormat = customFormat; + if (order !== undefined) updateData.order = order; + + const template = await templateDb.updateTemplate(templateId, updateData); + + let generationResult = null; + + // 如果启用自动生成,则为还未创建此模板问题的数据源创建问题 + if (autoGenerate) { + try { + generationResult = await generateQuestionsFromTemplateEdit(projectId, template); + } catch (error) { + console.error('编辑模式自动生成问题失败:', error); + generationResult = { + success: false, + successCount: 0, + failCount: 0, + message: '自动生成问题时发生错误' + }; + } + } + + return NextResponse.json({ + success: true, + template, + generation: generationResult + }); + } catch (error) { + console.error('Failed to update template:', error); + return NextResponse.json({ error: error.message || 'Failed to update template' }, { status: 500 }); + } +} + +// 删除问题模板 +export async function DELETE(request, { params }) { + try { + const { templateId } = params; + + // 检查是否有关联的问题 + const usageCount = await templateDb.getTemplateUsageCount(templateId); + if (usageCount > 0) { + return NextResponse.json({ error: `此模板已被 ${usageCount} 个问题使用,无法删除` }, { status: 400 }); + } + + await templateDb.deleteTemplate(templateId); + + return NextResponse.json({ + success: true, + message: '模板删除成功' + }); + } catch (error) { + console.error('Failed to delete template:', error); + return NextResponse.json({ error: error.message || 'Failed to delete template' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/questions/templates/route.js b/easy-dataset-main/app/api/projects/[projectId]/questions/templates/route.js new file mode 100644 index 0000000..4c3c795 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/questions/templates/route.js @@ -0,0 +1,116 @@ +import { NextResponse } from 'next/server'; +import templateDb from '@/lib/db/questionTemplates'; +import { generateQuestionsFromTemplate, checkTemplateGenerationAvailability } from '@/lib/services/questions/template'; + +// 获取问题模板列表 +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const sourceType = searchParams.get('sourceType'); + const search = searchParams.get('search'); + + const templates = await templateDb.getTemplates(projectId, { sourceType, search }); + + // 获取使用统计 + const templateIds = templates.map(t => t.id); + const usageCounts = await templateDb.getTemplatesUsageCount(templateIds); + + // 添加使用统计到模板数据 + const templatesWithUsage = templates.map(template => ({ + ...template, + usageCount: usageCounts[template.id] || 0 + })); + + return NextResponse.json({ + success: true, + templates: templatesWithUsage + }); + } catch (error) { + console.error('Failed to get templates:', error); + return NextResponse.json({ error: error.message || 'Failed to get templates' }, { status: 500 }); + } +} + +// 创建问题模板 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const data = await request.json(); + + const { question, sourceType, answerType, description, labels, customFormat, order, autoGenerate } = data; + + // 验证必填字段 + if (!question || !sourceType || !answerType) { + return NextResponse.json({ error: '缺少必要参数:question, sourceType, answerType' }, { status: 400 }); + } + + // 验证数据源类型 + if (!['image', 'text'].includes(sourceType)) { + return NextResponse.json({ error: '无效的数据源类型' }, { status: 400 }); + } + + // 验证答案类型 + if (!['text', 'label', 'custom_format'].includes(answerType)) { + return NextResponse.json({ error: '无效的答案类型' }, { status: 400 }); + } + + // 如果是标签类型,验证 labels + if (answerType === 'label' && (!labels || !Array.isArray(labels) || labels.length === 0)) { + return NextResponse.json({ error: '标签类型问题必须提供标签列表' }, { status: 400 }); + } + + // 如果是自定义格式,验证 customFormat + if (answerType === 'custom_format' && !customFormat) { + return NextResponse.json({ error: '自定义格式问题必须提供格式定义' }, { status: 400 }); + } + + const template = await templateDb.createTemplate(projectId, { + question, + sourceType, + answerType, + description, + labels: answerType === 'label' ? labels : [], + customFormat: answerType === 'custom_format' ? customFormat : null, + order: order || 0 + }); + + let generationResult = null; + + // 如果启用自动生成,则为所有相关数据源创建问题 + if (autoGenerate) { + try { + // 先检查是否有可用的数据源 + const availability = await checkTemplateGenerationAvailability(projectId, sourceType); + + if (availability.available) { + generationResult = await generateQuestionsFromTemplate(projectId, template); + } else { + generationResult = { + success: false, + successCount: 0, + failCount: 0, + message: availability.message + }; + } + } catch (error) { + console.error('自动生成问题失败:', error); + generationResult = { + success: false, + successCount: 0, + failCount: 0, + message: '自动生成问题时发生错误' + }; + } + } + + return NextResponse.json({ + success: true, + template, + generation: generationResult + }); + } catch (error) { + console.error('Failed to create template:', error); + return NextResponse.json({ error: error.message || 'Failed to create template' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/questions/tree/route.js b/easy-dataset-main/app/api/projects/[projectId]/questions/tree/route.js new file mode 100644 index 0000000..8ee67db --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/questions/tree/route.js @@ -0,0 +1,44 @@ +import { NextResponse } from 'next/server'; +import { getQuestionsForTree, getQuestionsByTag } from '@/lib/db/questions'; + +/** + * 获取项目的问题树形视图数据 + * @param {Request} request - 请求对象 + * @param {Object} params - 路由参数 + * @returns {Promise} - 包含问题数据的响应 + */ +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 }); + } + + const { searchParams } = new URL(request.url); + const tag = searchParams.get('tag'); + const input = searchParams.get('input'); + const tagsOnly = searchParams.get('tagsOnly') === 'true'; + const isDistill = searchParams.get('isDistill') === 'true'; + // 默认排除图片问题(label='image'),可通过 excludeImage=false 参数改变 + const excludeImage = searchParams.get('excludeImage') !== 'false'; + + if (tag) { + // 获取指定标签的问题数据(包含完整字段) + const questions = await getQuestionsByTag(projectId, tag, input, isDistill, excludeImage); + return NextResponse.json(questions); + } else if (tagsOnly) { + // 只获取标签信息(仅包含 id 和 label 字段) + const treeData = await getQuestionsForTree(projectId, input, isDistill, excludeImage); + return NextResponse.json(treeData); + } else { + // 兼容原有请求,获取树形视图数据(仅包含 id 和 label 字段) + const treeData = await getQuestionsForTree(projectId, null, isDistill, excludeImage); + return NextResponse.json(treeData); + } + } catch (error) { + console.error('获取问题树形数据失败:', String(error)); + return NextResponse.json({ error: error.message || '获取问题树形数据失败' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/route.js new file mode 100644 index 0000000..c863428 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/route.js @@ -0,0 +1,65 @@ +// 获取项目详情 +import { deleteProject, getProject, updateProject, getTaskConfig } from '@/lib/db/projects'; + +export async function GET(request, { params }) { + try { + const { projectId } = params; + const project = await getProject(projectId); + const taskConfig = await getTaskConfig(projectId); + if (!project) { + return Response.json({ error: '项目不存在' }, { status: 404 }); + } + return Response.json({ ...project, taskConfig }); + } catch (error) { + console.error('获取项目详情出错:', String(error)); + return Response.json({ error: String(error) }, { status: 500 }); + } +} + +// 更新项目 +export async function PUT(request, { params }) { + try { + const { projectId } = params; + const projectData = await request.json(); + + const hasNameField = Object.prototype.hasOwnProperty.call(projectData, 'name'); + const hasDefaultModelField = Object.prototype.hasOwnProperty.call(projectData, 'defaultModelConfigId'); + + // 至少允许更新名称或默认模型(defaultModelConfigId 可显式为 null) + if (!hasNameField && !hasDefaultModelField) { + return Response.json({ error: '项目名称不能为空' }, { status: 400 }); + } + + if (hasNameField && !projectData.name && !hasDefaultModelField) { + return Response.json({ error: '项目名称不能为空' }, { status: 400 }); + } + + const updatedProject = await updateProject(projectId, projectData); + + if (!updatedProject) { + return Response.json({ error: '项目不存在' }, { status: 404 }); + } + + return Response.json(updatedProject); + } catch (error) { + console.error('更新项目出错:', String(error)); + return Response.json({ error: String(error) }, { status: 500 }); + } +} + +// 删除项目 +export async function DELETE(request, { params }) { + try { + const { projectId } = params; + const success = await deleteProject(projectId); + + if (!success) { + return Response.json({ error: '项目不存在' }, { status: 404 }); + } + + return Response.json({ success: true }); + } catch (error) { + console.error('删除项目出错:', error); + return Response.json({ error: error.message }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/split/route.js b/easy-dataset-main/app/api/projects/[projectId]/split/route.js new file mode 100644 index 0000000..7ab799f --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/split/route.js @@ -0,0 +1,88 @@ +import { NextResponse } from 'next/server'; +import { splitProjectFile, getProjectChunks } from '@/lib/file/text-splitter'; +import { getProject, updateProject } from '@/lib/db/projects'; +import { getTags } from '@/lib/db/tags'; +import { handleDomainTree } from '@/lib/util/domain-tree'; + +// 处理文本分割请求 +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // 获取请求体 + const { fileNames, model, language, domainTreeAction = 'rebuild' } = await request.json(); + + if (!model) { + return NextResponse.json({ error: 'Please Select Model' }, { status: 400 }); + } + + const project = await getProject(projectId); + + let result = { + totalChunks: 0, + chunks: [], + toc: '' + }; + for (let i = 0; i < fileNames.length; i++) { + const fileName = fileNames[i]; + // 分割文本 + const { toc, chunks, totalChunks } = await splitProjectFile(projectId, fileName); + result.toc += toc; + result.chunks.push(...chunks); + result.totalChunks += totalChunks; + console.log(projectId, fileName, `Text split completed, ${domainTreeAction} domain tree`); + } + + // 调用领域树处理模块 + const tags = await handleDomainTree({ + projectId, + action: domainTreeAction, + newToc: result.toc, + model, + language, + fileNames, + project + }); + + if (!tags && domainTreeAction !== 'keep') { + await updateProject(projectId, { ...project }); + return NextResponse.json( + { error: 'AI analysis failed, please check model configuration, delete file and retry!' }, + { status: 400 } + ); + } + + return NextResponse.json({ ...result, tags }); + } catch (error) { + console.error('Text split error:', String(error)); + return NextResponse.json({ error: error.message || 'Text split failed' }, { status: 500 }); + } +} + +// 获取项目中的所有文本块 +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + const filter = searchParams.get('filter'); + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'The project ID cannot be empty' }, { status: 400 }); + } + + // 获取文本块详细信息 + const result = await getProjectChunks(projectId, filter); + + const tags = await getTags(projectId); + + // 返回详细的文本块信息和文件结果(单个文件) + return NextResponse.json({ + chunks: result.chunks, + ...result.fileResult, // 单个文件结果,而不是数组 + tags + }); + } catch (error) { + console.error('Failed to get text chunks:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to get text chunks' }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/tags/route.js b/easy-dataset-main/app/api/projects/[projectId]/tags/route.js new file mode 100644 index 0000000..e5b1392 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/tags/route.js @@ -0,0 +1,102 @@ +import { NextResponse } from 'next/server'; +import { getTags, createTag, updateTag, deleteTag } from '@/lib/db/tags'; +import { getQuestionsByTagName } from '@/lib/db/questions'; + +// 获取项目的标签树 +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + // 获取标签树 + const tags = await getTags(projectId); + + return NextResponse.json({ tags }); + } catch (error) { + console.error('Failed to obtain the label tree:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to obtain the label tree' }, { status: 500 }); + } +} + +// 更新项目的标签树 +export async function PUT(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + // 获取请求体 + const { tags } = await request.json(); + if (tags.id === undefined || tags.id === null || tags.id === '') { + console.log('createTag', tags); + let res = await createTag(projectId, tags.label, tags.parentId); + return NextResponse.json({ tags: res }); + } else { + let res = await updateTag(tags.label, tags.id); + return NextResponse.json({ tags: res }); + } + } catch (error) { + console.error('Failed to update tags:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to update tags' }, { status: 500 }); + } +} + +export async function POST(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + const { tagName } = await request.json(); + console.log('tagName', tagName); + let data = await getQuestionsByTagName(projectId, tagName); + + return NextResponse.json(data); + } catch (error) { + console.error('Failed to obtain the label tree:', String(error)); + return NextResponse.json({ error: error.message || 'Failed to obtain the label tree' }, { status: 500 }); + } +} + +export async function DELETE(request, { params }) { + try { + const { projectId } = params; + + // 验证项目ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + // 获取要删除的标签ID + const { searchParams } = new URL(request.url); + const id = searchParams.get('id'); + + if (!id) { + return NextResponse.json({ error: '标签 ID 是必需的' }, { status: 400 }); + } + + console.log(`正在删除标签: ${id}`); + const result = await deleteTag(id); + console.log(`删除标签成功: ${id}`); + + return NextResponse.json({ success: true, message: '删除标签成功', data: result }); + } catch (error) { + console.error('删除标签失败:', String(error)); + return NextResponse.json( + { + error: error.message || '删除标签失败', + success: false + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/tasks/[taskId]/route.js b/easy-dataset-main/app/api/projects/[projectId]/tasks/[taskId]/route.js new file mode 100644 index 0000000..6f13f95 --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/tasks/[taskId]/route.js @@ -0,0 +1,171 @@ +import { NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +// 获取任务详情 +export async function GET(request, { params }) { + try { + const { projectId, taskId } = params; + + // 验证必填参数 + if (!projectId || !taskId) { + return NextResponse.json( + { + code: 400, + error: '缺少必要参数' + }, + { status: 400 } + ); + } + + // 查询任务详情 + const task = await prisma.task.findUnique({ + where: { + id: taskId, + projectId + } + }); + + if (!task) { + return NextResponse.json( + { + code: 404, + error: '任务不存在' + }, + { status: 404 } + ); + } + + return NextResponse.json({ + code: 0, + data: task, + message: '获取任务详情成功' + }); + } catch (error) { + console.error('获取任务详情失败:', String(error)); + return NextResponse.json( + { + code: 500, + error: '获取任务详情失败', + message: error.message + }, + { status: 500 } + ); + } +} + +// 更新任务状态 +export async function PATCH(request, { params }) { + try { + const { projectId, taskId } = params; + const data = await request.json(); + + // 验证必填参数 + if (!projectId || !taskId) { + return NextResponse.json( + { + code: 400, + error: '缺少必要参数' + }, + { status: 400 } + ); + } + + // 获取要更新的字段 + const { status, completedCount, totalCount, detail, note, endTime } = data; + + // 构建更新数据 + const updateData = {}; + + if (status !== undefined) { + updateData.status = status; + } + + if (completedCount !== undefined) { + updateData.completedCount = completedCount; + } + + if (totalCount !== undefined) { + updateData.totalCount = totalCount; + } + + if (detail !== undefined) { + updateData.detail = detail; + } + + if (note !== undefined) { + updateData.note = note; + } + + // 如果状态变为已完成、失败或已中断,自动添加结束时间 + if (status === 1 || status === 2 || status === 3) { + updateData.endTime = endTime || new Date(); + } + + // 更新任务 + const updatedTask = await prisma.task.update({ + where: { + id: taskId + }, + data: updateData + }); + + return NextResponse.json({ + code: 0, + data: updatedTask, + message: '更新任务状态成功' + }); + } catch (error) { + console.error('更新任务状态失败:', String(error)); + return NextResponse.json( + { + code: 500, + error: '更新任务状态失败', + message: error.message + }, + { status: 500 } + ); + } +} + +// 删除任务 +export async function DELETE(request, { params }) { + try { + const { projectId, taskId } = params; + + // 验证必填参数 + if (!projectId || !taskId) { + return NextResponse.json( + { + code: 400, + error: '缺少必要参数' + }, + { status: 400 } + ); + } + + // 删除任务 + await prisma.task.delete({ + where: { + id: taskId, + projectId + } + }); + + return NextResponse.json({ + code: 0, + message: '删除任务成功' + }); + } catch (error) { + console.error('删除任务失败:', String(error)); + return NextResponse.json( + { + code: 500, + error: '删除任务失败', + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/tasks/list/route.js b/easy-dataset-main/app/api/projects/[projectId]/tasks/list/route.js new file mode 100644 index 0000000..1c6044c --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/tasks/list/route.js @@ -0,0 +1,63 @@ +import { NextResponse } from 'next/server'; +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +// 获取项目的所有任务列表 +export async function GET(request, { params }) { + try { + const { projectId } = params; + const { searchParams } = new URL(request.url); + + // 可选参数: 任务类型和任务状态 + const taskType = searchParams.get('taskType'); + const statusStr = searchParams.get('status'); + + // 分页参数 + const page = parseInt(searchParams.get('page') || '0'); + const limit = parseInt(searchParams.get('limit') || '10'); + + // 构建查询条件 + const where = { projectId }; + + if (taskType) { + where.taskType = taskType; + } + + if (statusStr && !isNaN(parseInt(statusStr))) { + where.status = parseInt(statusStr); + } + + // 获取任务总数 + const total = await prisma.task.count({ where }); + + // 获取任务列表,按创建时间降序排序,并应用分页 + const tasks = await prisma.task.findMany({ + where, + orderBy: { + createAt: 'desc' + }, + skip: page * limit, + take: limit + }); + + return NextResponse.json({ + code: 0, + data: tasks, + total, + page, + limit, + message: '任务列表获取成功' + }); + } catch (error) { + console.error('获取任务列表失败:', String(error)); + return NextResponse.json( + { + code: 500, + error: '获取任务列表失败', + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/[projectId]/tasks/route.js b/easy-dataset-main/app/api/projects/[projectId]/tasks/route.js new file mode 100644 index 0000000..c69821a --- /dev/null +++ b/easy-dataset-main/app/api/projects/[projectId]/tasks/route.js @@ -0,0 +1,164 @@ +import { NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs/promises'; +import { getProjectRoot } from '@/lib/db/base'; +import { getTaskConfig } from '@/lib/db/projects'; +import { processTask } from '@/lib/services/tasks'; +import { db } from '@/lib/db/index'; + +function normalizeModelEndpoint(endpoint = '') { + let normalizedEndpoint = String(endpoint).trim(); + if (!normalizedEndpoint) { + return ''; + } + if (normalizedEndpoint.includes('/chat/completions')) { + normalizedEndpoint = normalizedEndpoint.replace('/chat/completions', ''); + } + return normalizedEndpoint; +} + +function normalizeTaskModelInfo(modelInfo) { + if (!modelInfo) { + return {}; + } + let parsedModelInfo = modelInfo; + if (typeof modelInfo === 'string') { + try { + parsedModelInfo = JSON.parse(modelInfo); + } catch (error) { + return {}; + } + } + if (parsedModelInfo && typeof parsedModelInfo === 'object' && parsedModelInfo.endpoint) { + parsedModelInfo.endpoint = normalizeModelEndpoint(parsedModelInfo.endpoint); + } + return parsedModelInfo; +} + +// 获取任务配置 +export async function GET(request, { params }) { + try { + const { projectId } = params; + + // 验证项目 ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'Project does not exist' + projectPath }, { status: 404 }); + } + + const taskConfig = await getTaskConfig(projectId); + return NextResponse.json(taskConfig); + } catch (error) { + console.error('Failed to obtain task configuration:', String(error)); + return NextResponse.json({ error: 'Failed to obtain task configuration' }, { status: 500 }); + } +} + +// 更新任务配置 +export async function PUT(request, { params }) { + try { + const { projectId } = params; + + // 验证项目 ID + if (!projectId) { + return NextResponse.json({ error: 'Project ID is required' }, { status: 400 }); + } + + // 获取请求体 + const taskConfig = await request.json(); + + // 验证请求体 + if (!taskConfig) { + return NextResponse.json({ error: 'Task configuration cannot be empty' }, { status: 400 }); + } + + // 获取项目根目录 + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // 检查项目是否存在 + try { + await fs.access(projectPath); + } catch (error) { + return NextResponse.json({ error: 'Project does not exist' }, { status: 404 }); + } + + // 获取任务配置文件路径 + const taskConfigPath = path.join(projectPath, 'task-config.json'); + + // 写入任务配置文件 + await fs.writeFile(taskConfigPath, JSON.stringify(taskConfig, null, 2), 'utf-8'); + + return NextResponse.json({ message: 'Task configuration updated successfully' }); + } catch (error) { + console.error('Failed to update task configuration:', String(error)); + return NextResponse.json({ error: 'Failed to update task configuration' }, { status: 500 }); + } +} + +// 创建新任务 +export async function POST(request, { params }) { + try { + const { projectId } = params; + const data = await request.json(); + + // 验证必填字段 + const { taskType, modelInfo, language, detail = '', totalCount = 0, note } = data; + + if (!taskType) { + return NextResponse.json( + { + code: 400, + error: 'Missing required parameter: taskType' + }, + { status: 400 } + ); + } + + // 创建新任务 + const newTask = await db.task.create({ + data: { + projectId, + taskType, + status: 0, // 初始状态: 处理中 + modelInfo: JSON.stringify(normalizeTaskModelInfo(modelInfo)), + language: language || 'zh-CN', + detail: detail || '', + totalCount, + note: note ? JSON.stringify(note) : '', + completedCount: 0 + } + }); + + // 异步启动任务处理 + processTask(newTask.id).catch(err => { + console.error(`Task startup failed: ${newTask.id}`, String(err)); + }); + + return NextResponse.json({ + code: 0, + data: newTask, + message: 'Task created successfully' + }); + } catch (error) { + console.error('Failed to create task:', String(error)); + return NextResponse.json( + { + code: 500, + error: 'Failed to create task', + message: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/delete-directory/route.js b/easy-dataset-main/app/api/projects/delete-directory/route.js new file mode 100644 index 0000000..bde0bbf --- /dev/null +++ b/easy-dataset-main/app/api/projects/delete-directory/route.js @@ -0,0 +1,59 @@ +import { getProjectRoot } from '@/lib/db/base'; +import { NextResponse } from 'next/server'; +import path from 'path'; +import fs from 'fs'; +import { promisify } from 'util'; + +const rmdir = promisify(fs.rm); + +/** + * Delete project directory + * @returns {Promise} Operation result response + */ +export async function POST(request) { + try { + const { projectId } = await request.json(); + + if (!projectId) { + return NextResponse.json( + { + success: false, + error: 'Project ID is required' + }, + { status: 400 } + ); + } + + // Get project root directory + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // Check if directory exists + if (!fs.existsSync(projectPath)) { + return NextResponse.json( + { + success: false, + error: 'Project directory not found' + }, + { status: 404 } + ); + } + + // Recursively remove directory + await rmdir(projectPath, { recursive: true, force: true }); + + return NextResponse.json({ + success: true, + message: 'Project directory deleted' + }); + } catch (error) { + console.error('Failed to delete project directory:', String(error)); + return NextResponse.json( + { + success: false, + error: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/migrate/route.js b/easy-dataset-main/app/api/projects/migrate/route.js new file mode 100644 index 0000000..2ca6b1c --- /dev/null +++ b/easy-dataset-main/app/api/projects/migrate/route.js @@ -0,0 +1,169 @@ +import { NextResponse } from 'next/server'; +import { main } from '@/lib/db/fileToDb'; + +// Store migration task states +const migrationTasks = new Map(); + +/** + * Start a migration task + */ +export async function POST() { + try { + // Generate a unique task ID + const taskId = Date.now().toString(); + + // Initialize task state + migrationTasks.set(taskId, { + status: 'running', + progress: 0, + total: 0, + completed: 0, + error: null, + startTime: Date.now() + }); + + // Execute migration asynchronously + executeMigration(taskId); + + // Return task ID + return NextResponse.json({ + success: true, + taskId + }); + } catch (error) { + console.error('Failed to start migration task:', String(error)); + return NextResponse.json( + { + success: false, + error: error.message + }, + { status: 500 } + ); + } +} + +/** + * Get migration task status + */ +export async function GET(request) { + try { + // Get task ID from URL + const { searchParams } = new URL(request.url); + const taskId = searchParams.get('taskId'); + + if (!taskId) { + return NextResponse.json( + { + success: false, + error: 'Missing taskId' + }, + { status: 400 } + ); + } + + // Read task state + const task = migrationTasks.get(taskId); + + if (!task) { + return NextResponse.json( + { + success: false, + error: 'Task not found' + }, + { status: 404 } + ); + } + + // Return task state + return NextResponse.json({ + success: true, + task + }); + } catch (error) { + console.error('Failed to get migration task status:', String(error)); + return NextResponse.json( + { + success: false, + error: error.message + }, + { status: 500 } + ); + } +} + +/** + * Execute migration task asynchronously + * @param {string} taskId Task ID + */ +async function executeMigration(taskId) { + try { + // Read task state + const task = migrationTasks.get(taskId); + + if (!task) { + console.error(`Task not found: ${taskId}`); + return; + } + + // Reset task state to running + task.status = 'running'; + task.progress = 0; + task.completed = 0; + task.total = 0; + task.startTime = Date.now(); + + // Persist task state once per second so clients can poll progress + const statusUpdateInterval = setInterval(() => { + // Only update while still running + if (task.status === 'running') { + migrationTasks.set(taskId, { ...task }); + console.log( + `Migration task status updated: ${taskId}, progress: ${task.progress}%, completed: ${task.completed}/${task.total}` + ); + } else { + // Stop updating when task ends + clearInterval(statusUpdateInterval); + } + }, 1000); + + // Run migration and let main(task) mutate progress fields + const count = await main(task); + + // Clear status update timer + clearInterval(statusUpdateInterval); + + // Mark as completed + task.status = 'completed'; + task.progress = 100; + task.completed = count; + if (task.total === 0) task.total = count; + task.endTime = Date.now(); + + // Persist final task state + migrationTasks.set(taskId, { ...task }); + + // Clean up task state after 30 minutes + setTimeout( + () => { + migrationTasks.delete(taskId); + console.log(`Migration task state cleaned up: ${taskId}`); + }, + 30 * 60 * 1000 + ); + } catch (error) { + console.error(`Failed to execute migration task: ${taskId}`, String(error)); + + // Read task state + const task = migrationTasks.get(taskId); + + if (task) { + // Mark as failed + task.status = 'failed'; + task.error = error.message; + task.endTime = Date.now(); + + // Persist task state + migrationTasks.set(taskId, task); + } + } +} diff --git a/easy-dataset-main/app/api/projects/open-directory/route.js b/easy-dataset-main/app/api/projects/open-directory/route.js new file mode 100644 index 0000000..6d7bfe0 --- /dev/null +++ b/easy-dataset-main/app/api/projects/open-directory/route.js @@ -0,0 +1,62 @@ +import { getProjectRoot } from '@/lib/db/base'; +import { NextResponse } from 'next/server'; +import path from 'path'; +import { exec } from 'child_process'; +import { promisify } from 'util'; + +const execAsync = promisify(exec); + +/** + * Open project directory + * @returns {Promise} Operation result response + */ +export async function POST(request) { + try { + const { projectId } = await request.json(); + + if (!projectId) { + return NextResponse.json( + { + success: false, + error: 'Project ID is required' + }, + { status: 400 } + ); + } + + // Get project root directory + const projectRoot = await getProjectRoot(); + const projectPath = path.join(projectRoot, projectId); + + // Open directory based on OS + const platform = process.platform; + let command; + + if (platform === 'win32') { + // Windows + command = `explorer "${projectPath}"`; + } else if (platform === 'darwin') { + // macOS + command = `open "${projectPath}"`; + } else { + // Linux and others + command = `xdg-open "${projectPath}"`; + } + + await execAsync(command); + + return NextResponse.json({ + success: true, + message: 'Project directory opened' + }); + } catch (error) { + console.error('Failed to open project directory:', String(error)); + return NextResponse.json( + { + success: false, + error: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/projects/route.js b/easy-dataset-main/app/api/projects/route.js new file mode 100644 index 0000000..ee11911 --- /dev/null +++ b/easy-dataset-main/app/api/projects/route.js @@ -0,0 +1,42 @@ +import { createProject, getProjects, isExistByName } from '@/lib/db/projects'; +import { createInitModelConfig, getModelConfigByProjectId } from '@/lib/db/model-config'; + +export async function POST(request) { + try { + const projectData = await request.json(); + if (!projectData.name) { + return Response.json({ error: 'Project name is required' }, { status: 400 }); + } + + if (await isExistByName(projectData.name)) { + return Response.json({ error: 'Project name already exists' }, { status: 400 }); + } + const newProject = await createProject(projectData); + if (projectData.reuseConfigFrom) { + let data = await getModelConfigByProjectId(projectData.reuseConfigFrom); + + let newData = data.map(item => { + delete item.id; + return { + ...item, + projectId: newProject.id + }; + }); + await createInitModelConfig(newData); + } + return Response.json(newProject, { status: 201 }); + } catch (error) { + console.error('Failed to create project:', String(error)); + return Response.json({ error: String(error) }, { status: 500 }); + } +} + +export async function GET(request) { + try { + const projects = await getProjects(); + return Response.json(projects); + } catch (error) { + console.error('Failed to get project list:', String(error)); + return Response.json({ error: String(error) }, { status: 500 }); + } +} diff --git a/easy-dataset-main/app/api/projects/unmigrated/route.js b/easy-dataset-main/app/api/projects/unmigrated/route.js new file mode 100644 index 0000000..bf8e426 --- /dev/null +++ b/easy-dataset-main/app/api/projects/unmigrated/route.js @@ -0,0 +1,75 @@ +import { getProjectRoot } from '@/lib/db/base'; +import { db } from '@/lib/db/index'; +import fs from 'fs'; +import path from 'path'; +import { NextResponse } from 'next/server'; + +/** + * Get list of unmigrated projects + * @returns {Promise} Response containing unmigrated project IDs + */ +export async function GET(request) { + // Read query params from request URL + const { searchParams } = new URL(request.url); + // Force a unique value per request + const timestamp = searchParams.get('_t') || Date.now(); + try { + // Get project root directory + const projectRoot = await getProjectRoot(); + + // Read all folders under project root (each folder represents a project) + const files = await fs.promises.readdir(projectRoot, { withFileTypes: true }); + + // Filter directories + const projectDirs = files.filter(file => file.isDirectory()); + + // Return empty list if no project directories exist + if (projectDirs.length === 0) { + return NextResponse.json({ + success: true, + data: [] + }); + } + + // Collect all project IDs + const projectIds = projectDirs.map(dir => dir.name); + + // Batch query migrated projects + const existingProjects = await db.projects.findMany({ + where: { + id: { + in: projectIds + } + }, + select: { + id: true + } + }); + + // Convert to Set for fast lookups + const existingProjectIds = new Set(existingProjects.map(p => p.id)); + + // Filter unmigrated projects + const unmigratedProjectDirs = projectDirs.filter(dir => !existingProjectIds.has(dir.name)); + + // Build unmigrated project ID list + const unmigratedProjects = unmigratedProjectDirs.map(dir => dir.name); + + return NextResponse.json({ + success: true, + data: unmigratedProjects, + projectRoot, + number: Date.now(), + timestamp + }); + } catch (error) { + console.error('Failed to get unmigrated project list:', String(error)); + return NextResponse.json( + { + success: false, + error: error.message + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/api/update/route.js b/easy-dataset-main/app/api/update/route.js new file mode 100644 index 0000000..c70dd76 --- /dev/null +++ b/easy-dataset-main/app/api/update/route.js @@ -0,0 +1,70 @@ +import { NextResponse } from 'next/server'; +import { exec } from 'child_process'; +import path from 'path'; +import fs from 'fs'; + +export async function POST() { + try { + const desktopDir = path.join(process.cwd(), 'desktop'); + const updaterPath = path.join(desktopDir, 'scripts', 'updater.js'); + + if (!fs.existsSync(updaterPath)) { + return NextResponse.json( + { + success: false, + message: 'The update feature is only available in the client environment' + }, + { status: 400 } + ); + } + + // Run update script + return new Promise(resolve => { + const updaterProcess = exec(`node "${updaterPath}"`, { cwd: process.cwd() }); + + let output = ''; + + updaterProcess.stdout.on('data', data => { + output += data.toString(); + console.log(`Update output: ${data}`); + }); + + updaterProcess.stderr.on('data', data => { + output += data.toString(); + console.error(`Update error: ${data}`); + }); + + updaterProcess.on('close', code => { + console.log(`Update process exit, exit code: ${code}`); + + if (code === 0) { + resolve( + NextResponse.json({ + success: true, + message: 'Update successful, application will restart' + }) + ); + } else { + resolve( + NextResponse.json( + { + success: false, + message: `Update failed, exit code: ${code}, output: ${output}` + }, + { status: 500 } + ) + ); + } + }); + }); + } catch (error) { + console.error('Failed to execute update:', String(error)); + return NextResponse.json( + { + success: false, + message: `Failed to execute update: ${error.message}` + }, + { status: 500 } + ); + } +} diff --git a/easy-dataset-main/app/dataset-square/page.js b/easy-dataset-main/app/dataset-square/page.js new file mode 100644 index 0000000..2f7f46b --- /dev/null +++ b/easy-dataset-main/app/dataset-square/page.js @@ -0,0 +1,200 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Box, Container, Typography, Paper, useTheme, alpha } from '@mui/material'; +import StorageIcon from '@mui/icons-material/Storage'; +import Navbar from '@/components/Navbar/index'; +import { DatasetSearchBar } from '@/components/dataset-square/DatasetSearchBar'; +import { DatasetSiteList } from '@/components/dataset-square/DatasetSiteList'; +import { useTranslation } from 'react-i18next'; + +export default function DatasetSquarePage() { + const [projects, setProjects] = useState([]); + const theme = useTheme(); + const { t } = useTranslation(); + + // 获取项目列表和模型列表 + useEffect(() => { + async function fetchData() { + try { + // 获取用户创建的项目详情 + const response = await fetch('/api/projects'); + if (response.ok) { + const projectsData = await response.json(); + setProjects(projectsData); + } + } catch (error) { + console.error('获取数据失败:', error); + } + } + + fetchData(); + }, []); + + return ( +
+ {/* 导航栏 */} + + + {/* 头部区域 */} + + {/* 背景装饰 */} + + + + + + + + + {t('datasetSquare.title')} + + + + {t('datasetSquare.subtitle')} + + + {/* 搜索栏组件 */} + + + + + + + + + {/* 内容区域 */} + + {/* 数据集网站列表组件 */} + + + + +
+ ); +} diff --git a/easy-dataset-main/app/globals.css b/easy-dataset-main/app/globals.css new file mode 100644 index 0000000..8d042df --- /dev/null +++ b/easy-dataset-main/app/globals.css @@ -0,0 +1,122 @@ +* { + box-sizing: border-box; + padding: 0; + margin: 0; +} + +html, +body { + width: 100%; + max-width: 100%; + overflow-x: hidden; + height: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* 避免根滚动条显隐导致页面横向抖动 */ +html { + overflow-y: auto; +} + +a { + color: inherit; + text-decoration: none; +} + +/* 渐变文本样式 */ +.gradient-text { + background: linear-gradient(90deg, #2a5caa 0%, #8b5cf6 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-fill-color: transparent; +} + +/* 页面容器下间距 */ +main { + min-height: calc(100vh - 64px); +} + +/* 自定义滚动条 */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: transparent; +} + +::-webkit-scrollbar-thumb { + background-color: rgba(0, 0, 0, 0.2); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background-color: rgba(0, 0, 0, 0.3); +} + +/* 暗色模式滚动条 */ +[data-theme='dark'] ::-webkit-scrollbar-thumb { + background-color: rgba(255, 255, 255, 0.2); +} + +[data-theme='dark'] ::-webkit-scrollbar-thumb:hover { + background-color: rgba(255, 255, 255, 0.3); +} + +/* 方便的间距类 */ +.mt-1 { + margin-top: 8px; +} +.mt-2 { + margin-top: 16px; +} +.mt-3 { + margin-top: 24px; +} +.mt-4 { + margin-top: 32px; +} +.mb-1 { + margin-bottom: 8px; +} +.mb-2 { + margin-bottom: 16px; +} +.mb-3 { + margin-bottom: 24px; +} +.mb-4 { + margin-bottom: 32px; +} + +/* 响应式样式 */ +@media (max-width: 600px) { + .hide-on-mobile { + display: none !important; + } +} + +/* 输入框和选择框边框简化 */ +.plain-select .MuiOutlinedInput-notchedOutline, +.plain-input .MuiOutlinedInput-notchedOutline { + border-color: transparent !important; +} + +/* 卡片悬停效果 */ +.hover-card { + transition: + transform 0.2s ease, + box-shadow 0.2s ease; +} + +.hover-card:hover { + transform: translateY(-4px); + box-shadow: 0 12px 20px rgba(0, 0, 0, 0.1); +} + +[data-theme='dark'] .hover-card:hover { + box-shadow: 0 12px 20px rgba(0, 0, 0, 0.3); +} diff --git a/easy-dataset-main/app/layout.js b/easy-dataset-main/app/layout.js new file mode 100644 index 0000000..d6a4558 --- /dev/null +++ b/easy-dataset-main/app/layout.js @@ -0,0 +1,30 @@ +import './globals.css'; +import ThemeRegistry from '@/components/ThemeRegistry'; +import I18nProvider from '@/components/I18nProvider'; +import { Toaster } from 'sonner'; +import { Provider } from 'jotai'; + +export const metadata = { + title: 'Easy Dataset', + description: '一个强大的 LLM 数据集生成工具', + icons: { + icon: '/imgs/logo.ico' // 更新为正确的文件名 + } +}; + +export default function RootLayout({ children }) { + return ( + + + + + + {children} + + + + + + + ); +} diff --git a/easy-dataset-main/app/monitoring/components/Charts.js b/easy-dataset-main/app/monitoring/components/Charts.js new file mode 100644 index 0000000..8a01418 --- /dev/null +++ b/easy-dataset-main/app/monitoring/components/Charts.js @@ -0,0 +1,188 @@ +import React from 'react'; +import { Card, CardContent, Typography, Box, useTheme } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { + AreaChart, + Area, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + PieChart, + Pie, + Cell, + Legend +} from 'recharts'; + +export default function Charts({ trendData, modelDistribution }) { + const theme = useTheme(); + const { t } = useTranslation(); + + const COLORS = [ + theme.palette.primary.main, + theme.palette.secondary.main, + theme.palette.success.main, + theme.palette.warning.main, + theme.palette.error.main, + theme.palette.info.main + ]; + + return ( + + {/* 趋势图 */} + + + + + + {t('monitoring.charts.tokenTrend')} + + + + + + {t('monitoring.charts.inputLegend')} + + + + + + {t('monitoring.charts.outputLegend')} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* 模型分布图 */} + + + + + {t('monitoring.charts.distributionTitle')} + + + {t('monitoring.charts.distributionSubtitle')} + + + + + + + {modelDistribution.map((entry, index) => ( + + ))} + + t('monitoring.charts.tokensTooltip', { value: (value / 1000).toFixed(1) })} + contentStyle={{ + backgroundColor: theme.palette.background.paper, + border: `1px solid ${theme.palette.divider}`, + borderRadius: 8 + }} + /> + ( + {value} + )} + /> + + + {/* 中间文字 */} + {/* + + {modelDistribution.length} + + + Models + + */} + + + + + + ); +} diff --git a/easy-dataset-main/app/monitoring/components/StatsCards.js b/easy-dataset-main/app/monitoring/components/StatsCards.js new file mode 100644 index 0000000..5b79630 --- /dev/null +++ b/easy-dataset-main/app/monitoring/components/StatsCards.js @@ -0,0 +1,145 @@ +import React from 'react'; +import { Box, Card, CardContent, Grid, Typography, Stack, useTheme, alpha } from '@mui/material'; +import { + Storage as StorageIcon, + Balance as BalanceIcon, + Bolt as BoltIcon, + AccessTime as AccessTimeIcon +} from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; + +function StatCard({ title, value, subValue, icon: Icon, color }) { + const theme = useTheme(); + + return ( + + + + + + + + {title} + + + + + {value} + + + {subValue && ( + + {subValue} + + )} + + + ); +} + +export default function StatsCards({ data }) { + const theme = useTheme(); + const { t } = useTranslation(); + + // 格式化数字 + const formatNumber = num => { + if (num >= 1000000) return `${(num / 1000000).toFixed(1)}M`; + if (num >= 1000) return `${(num / 1000).toFixed(1)}K`; + return num; + }; + + return ( + + {/* 总 Token 消耗 */} + + + + + {/* 平均 Token 消耗/次 */} + + + + + {/* 总调用次数 */} + + + + {t('monitoring.stats.successCalls', { count: formatNumber(data.successCalls) })} + + + · + + + {t('monitoring.stats.failedCalls', { count: formatNumber(data.failedCalls) })} + + {data.totalCalls > 0 && ( + + ({t('monitoring.stats.failureRate', { rate: ((data.failureRate || 0) * 100).toFixed(1) })}) + + )} + + } + icon={BoltIcon} + color={theme.palette.success.main} + /> + + + {/* 平均响应耗时 */} + + 0 + ? t('monitoring.stats.basedOnSuccessCalls', { count: formatNumber(data.successCalls) }) + : t('monitoring.stats.noSuccessCalls') + } + icon={AccessTimeIcon} + color={theme.palette.warning.main} + /> + + + ); +} diff --git a/easy-dataset-main/app/monitoring/components/UsageTable.js b/easy-dataset-main/app/monitoring/components/UsageTable.js new file mode 100644 index 0000000..9a3e466 --- /dev/null +++ b/easy-dataset-main/app/monitoring/components/UsageTable.js @@ -0,0 +1,215 @@ +import React, { useState } from 'react'; +import { + Box, + Card, + CardContent, + Typography, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Chip, + TextField, + InputAdornment, + TablePagination, + useTheme, + alpha, + Tooltip +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import { useTranslation } from 'react-i18next'; + +const statusColors = { + SUCCESS: 'success', + FAILED: 'error' +}; + +export default function UsageTable({ + data, + total, + page, + pageSize, + onPageChange, + onPageSizeChange, + searchTerm, + onSearchChange +}) { + const theme = useTheme(); + const { t } = useTranslation(); + + const handleChangePage = (event, newPage) => { + onPageChange(newPage + 1); // MUI uses 0-indexed, our API uses 1-indexed + }; + + const handleChangeRowsPerPage = event => { + onPageSizeChange(parseInt(event.target.value, 10)); + }; + + const handleSearchChange = event => { + onSearchChange(event.target.value); + }; + + // 直接使用传入的数据,分页和搜索已在后端完成 + + return ( + + + + + {t('monitoring.table.title')} + + + + + ) + }} + sx={{ width: 300 }} + /> + + + + + + + + {t('monitoring.table.columns.projectName')} + + + {t('monitoring.table.columns.provider')} + + + {t('monitoring.table.columns.model')} + + + {t('monitoring.table.columns.status')} + + + {t('monitoring.table.columns.failureReason')} + + + {t('monitoring.table.columns.inputTokens')} + + + {t('monitoring.table.columns.outputTokens')} + + + {t('monitoring.table.columns.totalTokens')} + + + {t('monitoring.table.columns.calls')} + + + {t('monitoring.table.columns.avgLatency')} + + + + + {data.map((row, index) => ( + + + + {row.projectName} + + + + + + + + {row.model} + + + + + + + {row.failureReason ? ( + + 20 ? row.failureReason.slice(0, 20) + '...' : row.failureReason + } + size="small" + color="error" + variant="soft" + sx={{ + maxWidth: 200, + bgcolor: alpha(theme.palette.error.main, 0.1), + color: theme.palette.error.dark, + cursor: 'pointer' + }} + /> + + ) : ( + '-' + )} + + {row.inputTokens.toLocaleString()} + {row.outputTokens.toLocaleString()} + + {row.totalTokens.toLocaleString()} + + {row.calls} + {row.avgLatency} + + ))} + {data.length === 0 && ( + + + {t('monitoring.table.empty')} + + + )} + +
+
+ +
+
+ ); +} diff --git a/easy-dataset-main/app/monitoring/hooks/useMonitoringData.js b/easy-dataset-main/app/monitoring/hooks/useMonitoringData.js new file mode 100644 index 0000000..6b7f0b4 --- /dev/null +++ b/easy-dataset-main/app/monitoring/hooks/useMonitoringData.js @@ -0,0 +1,128 @@ +import { useState, useEffect, useCallback } from 'react'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; + +export function useMonitoringData() { + const { t } = useTranslation(); + const [loading, setLoading] = useState(true); + const [summaryData, setSummaryData] = useState({ + summary: { + totalTokens: 0, + inputTokens: 0, + outputTokens: 0, + totalCalls: 0, + successCalls: 0, + failedCalls: 0, + totalLatency: 0, + avgLatency: 0, + avgTokensPerCall: 0, + failureRate: 0 + }, + trend: [], + modelDistribution: [], + projects: [], + providers: [] + }); + + const [logsData, setLogsData] = useState({ + details: [], + total: 0, + page: 1, + pageSize: 10, + totalPages: 0 + }); + + const [filters, setFilters] = useState({ + timeRange: '7d', + projectId: 'all', + provider: 'all', + status: 'all' + }); + + const [pagination, setPagination] = useState({ + page: 1, + pageSize: 10 + }); + + const [searchTerm, setSearchTerm] = useState(''); + + // 获取汇总数据 + const fetchSummary = useCallback(async () => { + try { + const response = await axios.get('/api/monitoring/summary', { + params: filters + }); + setSummaryData(response.data); + } catch (error) { + console.error('Failed to fetch monitoring summary:', error); + toast.error(t('monitoring.errors.fetchSummaryFailed')); + } + }, [filters, t]); + + // 获取日志列表 + const fetchLogs = useCallback(async () => { + try { + const response = await axios.get('/api/monitoring/logs', { + params: { + ...filters, + page: pagination.page, + pageSize: pagination.pageSize, + search: searchTerm + } + }); + setLogsData(response.data); + } catch (error) { + console.error('Failed to fetch monitoring logs:', error); + toast.error(t('monitoring.errors.fetchLogsFailed')); + } + }, [filters, pagination, searchTerm, t]); + + // 初始加载 + useEffect(() => { + const fetchData = async () => { + setLoading(true); + await Promise.all([fetchSummary(), fetchLogs()]); + setLoading(false); + }; + fetchData(); + }, [fetchSummary, fetchLogs]); + + const handleFilterChange = (key, value) => { + setFilters(prev => ({ ...prev, [key]: value })); + setPagination(prev => ({ ...prev, page: 1 })); // 重置到第一页 + }; + + const handlePageChange = newPage => { + setPagination(prev => ({ ...prev, page: newPage })); + }; + + const handlePageSizeChange = newPageSize => { + setPagination({ page: 1, pageSize: newPageSize }); + }; + + const handleSearchChange = term => { + setSearchTerm(term); + setPagination(prev => ({ ...prev, page: 1 })); // 重置到第一页 + }; + + const refresh = useCallback(async () => { + setLoading(true); + await Promise.all([fetchSummary(), fetchLogs()]); + setLoading(false); + }, [fetchSummary, fetchLogs]); + + return { + loading, + summaryData, + logsData, + filters, + pagination, + searchTerm, + handleFilterChange, + handlePageChange, + handlePageSizeChange, + handleSearchChange, + refresh + }; +} diff --git a/easy-dataset-main/app/monitoring/page.js b/easy-dataset-main/app/monitoring/page.js new file mode 100644 index 0000000..9cfb87c --- /dev/null +++ b/easy-dataset-main/app/monitoring/page.js @@ -0,0 +1,244 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { + Box, + Container, + Stack, + Button, + FormControl, + Select, + MenuItem, + ToggleButton, + ToggleButtonGroup, + CircularProgress, + useTheme +} from '@mui/material'; +import { + Download as DownloadIcon, + FilterList as FilterListIcon, + CloudQueue as CloudQueueIcon, + CheckCircle as CheckCircleIcon +} from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import Navbar from '@/components/Navbar/index'; +import StatsCards from './components/StatsCards'; +import Charts from './components/Charts'; +import UsageTable from './components/UsageTable'; +import { useMonitoringData } from './hooks/useMonitoringData'; + +export default function MonitoringPage() { + const theme = useTheme(); + const { t } = useTranslation(); + const [projects, setProjects] = useState([]); + const { + loading, + summaryData, + logsData, + filters, + pagination, + searchTerm, + handleFilterChange, + handlePageChange, + handlePageSizeChange, + handleSearchChange + } = useMonitoringData(); + + // 获取项目列表用于 Navbar + useEffect(() => { + async function fetchProjects() { + try { + const response = await fetch('/api/projects'); + if (response.ok) { + const data = await response.json(); + setProjects(data); + } + } catch (error) { + console.error('Failed to fetch projects:', error); + } + } + fetchProjects(); + }, []); + + const handleTimeRangeChange = (event, newRange) => { + if (newRange !== null) { + handleFilterChange('timeRange', newRange); + } + }; + + const handleExport = () => { + // 简单的导出功能实现,将当前 logsData.details 导出为 CSV + if (!logsData.details || logsData.details.length === 0) return; + + const headers = [ + t('monitoring.table.columns.projectName'), + t('monitoring.table.columns.provider'), + t('monitoring.table.columns.model'), + t('monitoring.table.columns.status'), + t('monitoring.table.columns.failureReason'), + t('monitoring.table.columns.inputTokens'), + t('monitoring.table.columns.outputTokens'), + t('monitoring.table.columns.totalTokens'), + t('monitoring.table.columns.calls'), + t('monitoring.table.columns.avgLatency') + ]; + const csvContent = [ + headers.join(','), + ...logsData.details.map(row => + [ + row.projectName, + row.provider, + row.model, + row.status, + (row.failureReason || '').replace(/,/g, ' '), + row.inputTokens, + row.outputTokens, + row.totalTokens, + row.calls, + row.avgLatency + ].join(',') + ) + ].join('\n'); + + const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv;charset=utf-8;' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `llm-monitoring-export-${new Date().toISOString().slice(0, 10)}.csv`; + link.click(); + }; + + return ( + <> + + + + {/* Header Area */} + + {/* Time Range Selector */} + + {t('monitoring.timeRange.24h')} + {t('monitoring.timeRange.7d')} + {t('monitoring.timeRange.30d')} + + + {/* Filters & Actions */} + + + + + + + + + + + + + + + + + + {loading ? ( + + + + ) : ( + + {/* 统计卡片 */} + + + {/* 图表区域 */} + + + {/* 详细表格 */} + + + )} + + + + ); +} diff --git a/easy-dataset-main/app/page.js b/easy-dataset-main/app/page.js new file mode 100644 index 0000000..6dcc3c0 --- /dev/null +++ b/easy-dataset-main/app/page.js @@ -0,0 +1,153 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Container, Box, Typography, CircularProgress, Stack, useTheme } from '@mui/material'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import Navbar from '@/components/Navbar/index'; +import HeroSection from '@/components/home/HeroSection'; +import ProjectList from '@/components/home/ProjectList'; +import CreateProjectDialog from '@/components/home/CreateProjectDialog'; +import MigrationDialog from '@/components/home/MigrationDialog'; +import { motion } from 'framer-motion'; +import { useTranslation } from 'react-i18next'; + +export default function Home() { + const { t } = useTranslation(); + const [projects, setProjects] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [createDialogOpen, setCreateDialogOpen] = useState(false); + const [unmigratedProjects, setUnmigratedProjects] = useState([]); + const [migrationDialogOpen, setMigrationDialogOpen] = useState(false); + + useEffect(() => { + async function fetchProjects() { + try { + setLoading(true); + // 获取用户创建的项目详情 + const response = await fetch(`/api/projects`); + + if (!response.ok) { + throw new Error(t('projects.fetchFailed')); + } + + const data = await response.json(); + setProjects(data); + + // 检查是否有未迁移的项目 + await checkUnmigratedProjects(); + } catch (error) { + console.error(t('projects.fetchError'), String(error)); + setError(String(error)); + } finally { + setLoading(false); + } + } + + // 检查未迁移的项目 + async function checkUnmigratedProjects() { + try { + const response = await fetch('/api/projects/unmigrated'); + + if (!response.ok) { + console.error('检查未迁移项目失败'); + return; + } + + const { success, data } = await response.json(); + + if (success && Array.isArray(data) && data.length > 0) { + setUnmigratedProjects(data); + setMigrationDialogOpen(true); + } + } catch (error) { + console.error('检查未迁移项目出错', error); + } + } + + fetchProjects(); + }, []); + + const theme = useTheme(); + + return ( +
+ + + setCreateDialogOpen(true)} /> + + + {/* */} + + {loading && ( + + + + {t('projects.loading')} + + + )} + + {error && !loading && ( + + + + + {t('projects.fetchFailed')}: {error} + + + + )} + + {!loading && ( + + setCreateDialogOpen(true)} /> + + )} + + + setCreateDialogOpen(false)} /> + + {/* 项目迁移对话框 */} + setMigrationDialogOpen(false)} + projectIds={unmigratedProjects} + /> +
+ ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/[taskId]/page.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/[taskId]/page.js new file mode 100644 index 0000000..8af0afd --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/[taskId]/page.js @@ -0,0 +1,159 @@ +'use client'; + +import { useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { + Box, + Container, + Button, + CircularProgress, + Alert, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions +} from '@mui/material'; +import StopIcon from '@mui/icons-material/Stop'; +import { useTranslation } from 'react-i18next'; + +import useBlindTestDetail from '../hooks/useBlindTestDetail'; +import BlindTestHeader from '../components/BlindTestHeader'; +import ResultSummary from '../components/ResultSummary'; +import ResultDetailList from '../components/ResultDetailList'; +import BlindTestInProgress from '../components/BlindTestInProgress'; + +export default function BlindTestDetailPage() { + const { projectId, taskId } = useParams(); + const router = useRouter(); + const { t } = useTranslation(); + + const { + task, + loading, + error, + setError, + currentQuestion, + leftAnswer, + rightAnswer, + answersLoading, + streamingA, + streamingB, + voting, + completed, + fetchCurrentQuestion, + submitVote, + interruptTask, + getResultStats + } = useBlindTestDetail(projectId, taskId); + + const [interruptDialog, setInterruptDialog] = useState(false); + + const handleBack = () => router.push(`/projects/${projectId}/blind-test-tasks`); + + const handleVote = async vote => { + await submitVote(vote); + }; + + const handleInterrupt = async () => { + await interruptTask(); + setInterruptDialog(false); + }; + + // 加载中 + if (loading) { + return ( + + + + ); + } + + // 任务不存在 + if (!task) { + return ( + + + {t('blindTest.taskNotFound', '任务不存在')} + + ); + } + + const isResultView = completed || task.status !== 0; + const stats = getResultStats(); + + // 结果展示页面(已完成或已中断) + if (isResultView) { + return ( + + + + + + + + + ); + } + + // 盲测进行中页面 + return ( + + } + onClick={() => setInterruptDialog(true)} + size="small" + > + {t('blindTest.interrupt', '中断任务')} + + } + /> + + {error && ( + setError('')}> + {error} + + )} + + + + + + {/* 中断确认对话框 */} + setInterruptDialog(false)}> + {t('blindTest.interruptConfirmTitle', '确认中断')} + + + {t('blindTest.interruptConfirmMessage', '确定要中断这个盲测任务吗?已完成的评判结果将保留。')} + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestHeader.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestHeader.js new file mode 100644 index 0000000..cb30986 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestHeader.js @@ -0,0 +1,77 @@ +import { Box, Typography, IconButton, Chip, Button } from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import CompareArrowsIcon from '@mui/icons-material/CompareArrows'; +import { useTranslation } from 'react-i18next'; +import { useTheme, alpha } from '@mui/material/styles'; +import { blindTestStyles } from '@/styles/blindTest'; + +export default function BlindTestHeader({ title, status, onBack, actions }) { + const { t } = useTranslation(); + const theme = useTheme(); + const styles = blindTestStyles(theme); + + const getStatusConfig = s => { + switch (s) { + case 1: + return { label: 'blindTest.statusCompleted', color: 'success' }; + case 3: + return { label: 'blindTest.statusInterrupted', color: 'warning' }; + default: + return { label: 'blindTest.statusProcessing', color: 'primary' }; + } + }; + + const statusConfig = status !== undefined ? getStatusConfig(status) : null; + + return ( + + + + + + + + + + + {title} + + {statusConfig && ( + + )} + + + {actions} + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestInProgress.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestInProgress.js new file mode 100644 index 0000000..cc6d0b8 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestInProgress.js @@ -0,0 +1,449 @@ +import { useState, useRef, useEffect } from 'react'; +import { + Box, + Paper, + Typography, + Button, + LinearProgress, + CircularProgress, + Alert, + Chip, + Collapse, + IconButton, + Tooltip, + Fade, + Avatar +} from '@mui/material'; +import { useTheme, alpha } from '@mui/material/styles'; +import ThumbUpIcon from '@mui/icons-material/ThumbUp'; +import ThumbDownIcon from '@mui/icons-material/ThumbDown'; +import ThumbsUpDownIcon from '@mui/icons-material/ThumbsUpDown'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import PsychologyIcon from '@mui/icons-material/Psychology'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; +import AssignmentIcon from '@mui/icons-material/Assignment'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import { useTranslation } from 'react-i18next'; +import ReactMarkdown from 'react-markdown'; +import 'github-markdown-css/github-markdown-light.css'; +import { blindTestStyles } from '@/styles/blindTest'; + +function AnswerBox({ title, modelLabel, answer, streaming, showThinking, setShowThinking, scrollRef, styles, theme }) { + const { t } = useTranslation(); + const isLeft = modelLabel === 'A'; + const avatarColor = isLeft ? 'primary.main' : 'secondary.main'; + + return ( + + + + + {modelLabel} + + + {title} + + {streaming && } + + {answer?.duration > 0 && !streaming && ( + + )} + + + {answer?.error ? ( + + + {answer.error} + + + ) : ( + + {/* 思维链渲染 */} + {answer?.thinking && ( + + setShowThinking(!showThinking)} + > + + {answer.isThinking ? ( + + ) : ( + + )} + + {t('playground.reasoningProcess', '推理过程')} + + + + {showThinking ? : } + + + + + + {answer.thinking} + + + + + )} + + {answer?.content ? ( +
+ {answer.content} +
+ ) : streaming ? ( + + + {t('blindTest.generatingAnswers', '正在生成回答...')} + + ) : null} +
+ )} +
+ ); +} + +export default function BlindTestInProgress({ + task, + currentQuestion, + leftAnswer, + rightAnswer, + streamingA, + streamingB, + answersLoading, + voting, + onVote, + onReload +}) { + const { t } = useTranslation(); + const theme = useTheme(); + const styles = blindTestStyles(theme); + + const [showThinkingLeft, setShowThinkingLeft] = useState(true); + const [showThinkingRight, setShowThinkingRight] = useState(true); + + // 自动滚动引用 + const leftScrollRef = useRef(null); + const rightScrollRef = useRef(null); + + // 处理自动滚动 + useEffect(() => { + if (streamingA && leftScrollRef.current) { + leftScrollRef.current.scrollTop = leftScrollRef.current.scrollHeight; + } + }, [leftAnswer?.content, leftAnswer?.thinking, streamingA]); + + useEffect(() => { + if (streamingB && rightScrollRef.current) { + rightScrollRef.current.scrollTop = rightScrollRef.current.scrollHeight; + } + }, [rightAnswer?.content, rightAnswer?.thinking, streamingB]); + + const progress = task ? (task.completedCount / task.totalCount) * 100 : 0; + + if (answersLoading && !currentQuestion) { + return ( + + + + {t('blindTest.generatingAnswers', '正在准备题目...')} + + + ); + } + + if (!currentQuestion) { + return ( + + + + ); + } + + return ( + + {/* 顶部进度和问题 */} + + + + + {t('blindTest.progress', '进度')} {task.completedCount + 1}/{task.totalCount} + + + + + + + + + {currentQuestion.question} + + + + + {/* 回答区域 */} + + + + + + {/* 底部投票区域 */} + + + + + + + + {t('blindTest.referenceAnswer', '参考答案')} + + + {currentQuestion.answer} + + + ) : ( + t('blindTest.noReferenceAnswer', '暂无参考答案') + ) + } + arrow + placement="top" + TransitionComponent={Fade} + TransitionProps={{ timeout: 600 }} + componentsProps={{ + tooltip: { + sx: { + bgcolor: theme.palette.mode === 'dark' ? 'grey.900' : 'background.paper', + color: 'text.primary', + boxShadow: theme.shadows[8], + border: `1px solid ${theme.palette.divider}`, + p: 0, + '& .MuiTooltip-arrow': { + color: theme.palette.mode === 'dark' ? 'grey.900' : 'background.paper', + '&::before': { + border: `1px solid ${theme.palette.divider}` + } + } + } + } + }} + > + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestTaskCard.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestTaskCard.js new file mode 100644 index 0000000..3fdb8e3 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/BlindTestTaskCard.js @@ -0,0 +1,316 @@ +'use client'; + +import { + Box, + Card, + CardContent, + Typography, + Chip, + IconButton, + Menu, + MenuItem, + LinearProgress, + Avatar, + Grid, + Tooltip, + Divider +} from '@mui/material'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import DeleteIcon from '@mui/icons-material/Delete'; +import StopIcon from '@mui/icons-material/Stop'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { alpha, useTheme } from '@mui/material/styles'; + +const STATUS_MAP = { + 0: { label: 'blindTest.statusProcessing', color: 'primary', bgColor: 'primary.main' }, + 1: { label: 'blindTest.statusCompleted', color: 'success', bgColor: 'success.main' }, + 2: { label: 'blindTest.statusFailed', color: 'error', bgColor: 'error.main' }, + 3: { label: 'blindTest.statusInterrupted', color: 'warning', bgColor: 'warning.main' } +}; + +export default function BlindTestTaskCard({ task, onView, onDelete, onInterrupt, onContinue }) { + const { t } = useTranslation(); + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); + + const handleMenuOpen = e => { + e.stopPropagation(); + setAnchorEl(e.currentTarget); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const handleView = e => { + e?.stopPropagation?.(); + handleMenuClose(); + onView?.(task); + }; + + const handleDelete = e => { + e?.stopPropagation?.(); + handleMenuClose(); + onDelete?.(task); + }; + + const handleInterrupt = e => { + e?.stopPropagation?.(); + handleMenuClose(); + onInterrupt?.(task); + }; + + const handleContinue = e => { + e?.stopPropagation?.(); + handleMenuClose(); + onContinue?.(task); + }; + + const statusConfig = STATUS_MAP[task.status] || STATUS_MAP[0]; + const progress = task.totalCount > 0 ? (task.completedCount / task.totalCount) * 100 : 0; + const isProcessing = task.status === 0; + const isCompleted = task.status === 1; + + // 计算模型得分 + const results = task.detail?.results || []; + const modelAScore = results.reduce((sum, r) => sum + (r.modelAScore || 0), 0); + const modelBScore = results.reduce((sum, r) => sum + (r.modelBScore || 0), 0); + const totalScore = modelAScore + modelBScore; + + // Calculate win percentages for visual bar + const modelAPercent = totalScore > 0 ? (modelAScore / totalScore) * 100 : 50; + const modelBPercent = totalScore > 0 ? (modelBScore / totalScore) * 100 : 50; + + const winner = isCompleted ? (modelAScore > modelBScore ? 'A' : modelBScore > modelAScore ? 'B' : 'Tie') : null; + + return ( + handleView(e)} + > + + + {/* Status & Time */} + + + + + + + {new Date(task.createAt).toLocaleDateString()} + + + + + + {/* Model Comparison Area */} + + + {/* Model A */} + + + A + + + + + {task.modelInfo?.modelA?.modelName || 'Model A'} + + + + {task.modelInfo?.modelA?.providerName} + + + {isCompleted && winner === 'A' && } + + + {/* Center Status/Score */} + + {isCompleted ? ( + + + {modelAScore.toFixed(1)} + : + {modelBScore.toFixed(1)} + + + + + + + ) : ( + + + VS + + + + {Math.round(progress)}% + + + )} + + + {/* Model B */} + + + B + + + + + {task.modelInfo?.modelB?.modelName || 'Model B'} + + + + {task.modelInfo?.modelB?.providerName} + + + {isCompleted && winner === 'B' && } + + + + + {/* Menu */} + + + + + + + + + {/* 菜单 */} + + + + {t('blindTest.viewDetails', '查看详情')} + + {isProcessing && ( + + + {t('blindTest.continue', '继续盲测')} + + )} + {isProcessing && ( + + + {t('blindTest.interrupt', '中断任务')} + + )} + + + + {t('common.delete', '删除')} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/CreateBlindTestDialog.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/CreateBlindTestDialog.js new file mode 100644 index 0000000..f134c67 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/CreateBlindTestDialog.js @@ -0,0 +1,498 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + FormControl, + InputLabel, + Select, + MenuItem, + Alert, + CircularProgress, + Chip, + Divider, + TextField, + OutlinedInput, + Checkbox, + ListItemText, + Avatar, + Paper +} from '@mui/material'; +import CompareArrowsIcon from '@mui/icons-material/CompareArrows'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import { useTranslation } from 'react-i18next'; +import { alpha, useTheme } from '@mui/material/styles'; + +export default function CreateBlindTestDialog({ open, onClose, projectId, onCreate }) { + const { t } = useTranslation(); + const theme = useTheme(); + + // 模型选择 + const [models, setModels] = useState([]); + const [modelsLoading, setModelsLoading] = useState(false); + const [modelA, setModelA] = useState(null); + const [modelB, setModelB] = useState(null); + + // 题目选择 + const [questionTypes, setQuestionTypes] = useState(['short_answer', 'open_ended']); + const [selectedTags, setSelectedTags] = useState([]); + const [availableTags, setAvailableTags] = useState([]); + const [questionCount, setQuestionCount] = useState(0); + const [filteredCount, setFilteredCount] = useState(0); + const [countLoading, setCountLoading] = useState(false); + + // 提交状态 + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(''); + + // 加载模型列表 + useEffect(() => { + if (!open || !projectId) return; + + const fetchModels = async () => { + try { + setModelsLoading(true); + const response = await fetch(`/api/projects/${projectId}/model-config`); + const result = await response.json(); + + if (result.data) { + setModels(result.data); + } + } catch (err) { + console.error('加载模型失败:', err); + } finally { + setModelsLoading(false); + } + }; + + fetchModels(); + }, [open, projectId]); + + // 加载标签和题目数量 + useEffect(() => { + if (!open || !projectId) return; + + const fetchStats = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/eval-datasets?page=1&pageSize=1&includeStats=true`); + const result = await response.json(); + + if (result.stats?.byTag) { + setAvailableTags(Object.keys(result.stats.byTag).sort()); + } + } catch (err) { + console.error('加载统计失败:', err); + } + }; + + fetchStats(); + }, [open, projectId]); + + // 获取符合条件的题目数量 + const fetchFilteredCount = useCallback(async () => { + if (!projectId) return; + + try { + setCountLoading(true); + const params = new URLSearchParams(); + + // 只查询主观题 + questionTypes.forEach(t => params.append('questionTypes', t)); + selectedTags.forEach(t => params.append('tags', t)); + + const response = await fetch(`/api/projects/${projectId}/eval-datasets/count?${params.toString()}`); + const result = await response.json(); + + if (result.code === 0) { + setFilteredCount(result.data?.total || 0); + } + } catch (err) { + console.error('获取题目数量失败:', err); + } finally { + setCountLoading(false); + } + }, [projectId, questionTypes, selectedTags]); + + useEffect(() => { + if (open) { + fetchFilteredCount(); + } + }, [open, fetchFilteredCount]); + + // 重置表单 + const resetForm = () => { + setModelA(null); + setModelB(null); + setQuestionTypes(['short_answer', 'open_ended']); + setSelectedTags([]); + setQuestionCount(0); + setError(''); + }; + + // 关闭对话框 + const handleClose = () => { + if (submitting) return; + resetForm(); + onClose(); + }; + + // 提交创建 + const handleSubmit = async () => { + // 验证 + if (!modelA) { + setError(t('blindTest.errorSelectModelA', '请选择模型A')); + return; + } + if (!modelB) { + setError(t('blindTest.errorSelectModelB', '请选择模型B')); + return; + } + if (modelA.id === modelB.id) { + setError(t('blindTest.errorSameModel', '两个模型不能相同')); + return; + } + if (filteredCount === 0) { + setError(t('blindTest.errorNoQuestions', '没有符合条件的题目')); + return; + } + + try { + setSubmitting(true); + setError(''); + + // 获取题目ID列表 + const params = new URLSearchParams(); + questionTypes.forEach(t => params.append('questionTypes', t)); + selectedTags.forEach(t => params.append('tags', t)); + + const pageSize = questionCount > 0 ? questionCount : filteredCount; + params.append('pageSize', pageSize.toString()); + + const response = await fetch(`/api/projects/${projectId}/eval-datasets?${params.toString()}`); + const result = await response.json(); + + if (!result.items || result.items.length === 0) { + setError(t('blindTest.errorNoQuestions', '没有符合条件的题目')); + return; + } + + // 随机选择题目(如果指定了数量) + let selectedIds = result.items.map(item => item.id); + if (questionCount > 0 && questionCount < selectedIds.length) { + // 随机抽取 + selectedIds = selectedIds.sort(() => Math.random() - 0.5).slice(0, questionCount); + } + + // 创建任务 + const createResult = await onCreate({ + modelA: { modelId: modelA.modelId, providerId: modelA.providerId, id: modelA.id }, + modelB: { modelId: modelB.modelId, providerId: modelB.providerId, id: modelB.id }, + evalDatasetIds: selectedIds + }); + + if (createResult.success) { + handleClose(); + } else { + setError(createResult.error || '创建失败'); + } + } catch (err) { + console.error('创建任务失败:', err); + setError('创建任务失败'); + } finally { + setSubmitting(false); + } + }; + + const QUESTION_TYPES = [ + { value: 'short_answer', labelKey: 'eval.questionTypes.short_answer' }, + { value: 'open_ended', labelKey: 'eval.questionTypes.open_ended' } + ]; + + return ( + + + + + + + {t('blindTest.createTitle', '创建盲测任务')} + + + + + {error && ( + setError('')}> + {error} + + )} + + {/* 模型选择 */} + + + + {t('blindTest.selectModels', '选择对比模型')} + + + + + {/* 模型A */} + + + A + + {t('blindTest.modelA', '模型 A')} + + + + + + + + + + VS + + + + {/* 模型B */} + + + B + + {t('blindTest.modelB', '模型 B')} + + + + + + + + + {models.length === 0 && !modelsLoading && ( + + {t('blindTest.noModelsAvailable', '暂无可用模型,请先在设置中配置模型')} + + )} + + + + {/* 题目筛选 */} + + + {t('blindTest.selectQuestions', '选择测试题目')} + + + + {t('blindTest.questionTypeHint', '盲测任务仅支持简答题和开放题')} + + + + + {/* 题型筛选 */} + + {t('blindTest.questionType', '题型')} + + + + {/* 标签筛选 */} + {availableTags.length > 0 && ( + + {t('blindTest.filterByTag', '按标签筛选')} + + + )} + + + {/* 题目数量 */} + + + setQuestionCount(Math.max(0, parseInt(e.target.value) || 0))} + inputProps={{ min: 0, max: filteredCount }} + sx={{ width: 150, bgcolor: 'background.paper' }} + /> + + + + + {countLoading ? ( + + ) : ( + t('blindTest.availableQuestions', '可用题目:{{count}} 道', { count: filteredCount }) + )} + + {filteredCount > 0 && ( + + )} + + + {questionCount === 0 + ? t('blindTest.useAllQuestions', '使用全部筛选结果') + : t('blindTest.randomSample', '将随机抽取 {{count}} 道题目', { count: questionCount })} + + + + + + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/ResultDetailList.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/ResultDetailList.js new file mode 100644 index 0000000..5db9093 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/ResultDetailList.js @@ -0,0 +1,335 @@ +import { Box, Paper, Typography, Chip, Collapse, IconButton, Avatar, Divider, Grid } from '@mui/material'; +import ReactMarkdown from 'react-markdown'; +import { useTranslation } from 'react-i18next'; +import { useTheme, alpha } from '@mui/material/styles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import PsychologyIcon from '@mui/icons-material/Psychology'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import CancelIcon from '@mui/icons-material/Cancel'; +import RemoveCircleIcon from '@mui/icons-material/RemoveCircle'; +import HelpIcon from '@mui/icons-material/Help'; +import { useState } from 'react'; +import 'github-markdown-css/github-markdown-light.css'; + +// 解析包含 标签的内容 +const parseAnswerContent = text => { + if (!text) return { thinking: '', content: '' }; + + // 匹配 ... 内容 + const thinkMatch = text.match(/([\s\S]*?)<\/think>/); + + if (thinkMatch) { + return { + thinking: thinkMatch[1].trim(), + content: text.replace(/[\s\S]*?<\/think>/, '').trim() + }; + } + + return { thinking: '', content: text }; +}; + +function ResultAnswerSection({ title, rawContent, isWinner, modelLabel, t, theme }) { + const { thinking, content } = parseAnswerContent(rawContent); + const [showThinking, setShowThinking] = useState(false); + + const isLeft = modelLabel.includes('A') || title.includes('左'); + const avatarColor = isLeft ? 'primary.main' : 'secondary.main'; + + return ( + + + + + {modelLabel} + + + {title} + + + {isWinner && ( + } + label={t('blindTest.winner', '胜出')} + size="small" + color={isLeft ? 'primary' : 'secondary'} + sx={{ fontWeight: 600 }} + /> + )} + + + + {/* 思维链展示 */} + {thinking && ( + + setShowThinking(!showThinking)} + > + + + {t('playground.reasoningProcess', '推理过程')} + + + {showThinking ? ( + + ) : ( + + )} + + + + + + {thinking} + + + + + )} + + {/* 正文内容 */} +
+ {content || '-'} +
+
+
+ ); +} + +function ResultItem({ result, index, task, question }) { + const { t } = useTranslation(); + const theme = useTheme(); + const [expanded, setExpanded] = useState(false); + + // Determine vote icon and color + let VoteIcon = HelpIcon; + let voteColor = 'default'; + let voteLabel = ''; + + switch (result.vote) { + case 'left': + VoteIcon = CheckCircleIcon; + voteColor = 'primary'; + voteLabel = t('blindTest.leftBetter', '左边更好'); + break; + case 'right': + VoteIcon = CheckCircleIcon; + voteColor = 'secondary'; + voteLabel = t('blindTest.rightBetter', '右边更好'); + break; + case 'both_good': + VoteIcon = CheckCircleIcon; + voteColor = 'success'; + voteLabel = t('blindTest.bothGood', '都好'); + break; + case 'both_bad': + VoteIcon = CancelIcon; + voteColor = 'error'; + voteLabel = t('blindTest.bothBad', '都不好'); + break; + default: + VoteIcon = RemoveCircleIcon; + voteLabel = t('blindTest.ties', '平局'); + } + + // Determine Model labels based on swap status + const leftModelName = result.isSwapped ? task.modelInfo?.modelB?.modelName : task.modelInfo?.modelA?.modelName; + const rightModelName = result.isSwapped ? task.modelInfo?.modelA?.modelName : task.modelInfo?.modelB?.modelName; + const leftModelLabel = result.isSwapped ? 'B' : 'A'; + const rightModelLabel = result.isSwapped ? 'A' : 'B'; + + return ( + + {/* 头部摘要 */} + setExpanded(!expanded)} + > + + + #{index + 1} + + + {question?.question || result.questionId} + + + + + } + label={voteLabel} + color={voteColor === 'default' ? 'default' : voteColor} + variant={result.vote === 'both_good' || result.vote === 'both_bad' ? 'outlined' : 'filled'} + sx={{ fontWeight: 600 }} + /> + + + + + + + {/* 展开详情 */} + + + + + + QUESTION + + + {question?.question} + + + + + {/* 左侧详情 */} + + + + + {/* 右侧详情 */} + + + + + + + + ); +} + +export default function ResultDetailList({ task }) { + const { t } = useTranslation(); + + return ( + + + {t('blindTest.detailResults', '详细结果')} + + + {task.detail?.results?.map((result, index) => { + const question = task.evalDatasets?.find(q => q.id === result.questionId); + return ; + })} + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/ResultSummary.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/ResultSummary.js new file mode 100644 index 0000000..d51fd66 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/components/ResultSummary.js @@ -0,0 +1,257 @@ +import { Box, Paper, Typography, Card, CardContent, Chip, Grid, Avatar } from '@mui/material'; +import { useTheme, alpha } from '@mui/material/styles'; +import EmojiEventsIcon from '@mui/icons-material/EmojiEvents'; +import { useTranslation } from 'react-i18next'; +import { blindTestStyles } from '@/styles/blindTest'; + +export default function ResultSummary({ stats, modelInfo }) { + const { t } = useTranslation(); + const theme = useTheme(); + + if (!stats) return null; + + const totalScore = stats.modelAScore + stats.modelBScore; + const modelAPercent = totalScore > 0 ? (stats.modelAScore / totalScore) * 100 : 50; + const modelBPercent = totalScore > 0 ? (stats.modelBScore / totalScore) * 100 : 50; + + const winner = stats.modelAScore > stats.modelBScore ? 'A' : stats.modelBScore > stats.modelAScore ? 'B' : 'tie'; + + return ( + + + + {t('blindTest.resultSummary', '评测结果汇总')} + + + + {/* Model A */} + + + {winner === 'A' && ( + + WINNER + + )} + + + A + + + {modelInfo?.modelA?.modelName || 'Model A'} + + + + + + {stats.modelAScore.toFixed(1)} + + + {t('blindTest.wins', '胜出')}: {stats.modelAWins} + + + + + + + {/* VS / Progress */} + + + + VS + + + + + + + + + + + {/* Model B */} + + + {winner === 'B' && ( + + WINNER + + )} + + + B + + + {modelInfo?.modelB?.modelName || 'Model B'} + + + + + + {stats.modelBScore.toFixed(1)} + + + {t('blindTest.wins', '胜出')}: {stats.modelBWins} + + + + + + + + {/* 底部统计条 */} + + + + + + {stats.totalQuestions} + + + {t('blindTest.totalQuestions', '总题数')} + + + + + + + {stats.bothGood} + + + {t('blindTest.bothGood', '都好')} + + + + + + + {stats.bothBad} + + + {t('blindTest.bothBad', '都不好')} + + + + + + + {stats.ties} + + + {t('blindTest.ties', '平局')} + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestDetail.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestDetail.js new file mode 100644 index 0000000..7710183 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestDetail.js @@ -0,0 +1,405 @@ +'use client'; + +import { useState, useCallback, useEffect, useRef } from 'react'; + +/** + * 盲测任务详情和盲测过程管理 Hook + */ +export default function useBlindTestDetail(projectId, taskId) { + // 任务详情 + const [task, setTask] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + // 当前题目状态 + const [currentQuestion, setCurrentQuestion] = useState(null); + const [leftAnswer, setLeftAnswer] = useState(null); + const [rightAnswer, setRightAnswer] = useState(null); + const [isSwapped, setIsSwapped] = useState(false); + const [answersLoading, setAnswersLoading] = useState(false); + + // 流式输出状态 + const [streamingA, setStreamingA] = useState(false); + const [streamingB, setStreamingB] = useState(false); + const abortControllerRef = useRef(null); + const hasAutoLoadedRef = useRef(false); + + // 投票状态 + const [voting, setVoting] = useState(false); + const [completed, setCompleted] = useState(false); + + // 加载任务详情 + const loadTask = useCallback( + async (silent = false) => { + if (!projectId || !taskId) return; + + try { + if (!silent) setLoading(true); + setError(''); + // 添加时间戳防止缓存 + const response = await fetch(`/api/projects/${projectId}/blind-test-tasks/${taskId}?t=${Date.now()}`, { + cache: 'no-store', + headers: { + Pragma: 'no-cache', + 'Cache-Control': 'no-cache' + } + }); + const result = await response.json(); + + if (result.code === 0) { + console.log('任务状态更新:', result.data.completedCount, '/', result.data.totalCount); + setTask(result.data); + // 检查任务是否已完成 (0=进行中, 1=已完成, 2=失败, 3=已中断) + if (result.data.status !== 0) { + setCompleted(true); + } + } else { + if (!silent) setError(result.error || '加载任务详情失败'); + } + } catch (err) { + console.error('加载任务详情失败:', err); + if (!silent) setError('加载任务详情失败'); + } finally { + if (!silent) setLoading(false); + } + }, + [projectId, taskId] + ); + + // 流式获取当前题目和模型回答 + const fetchCurrentQuestion = useCallback(async () => { + if (!projectId || !taskId) return; + + // 取消上一次的请求 + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + const controller = new AbortController(); + abortControllerRef.current = controller; + + try { + setAnswersLoading(true); + setError(''); + setCurrentQuestion(null); + setLeftAnswer({ fullContent: '', content: '', thinking: '', isThinking: false, duration: 0, error: null }); + setRightAnswer({ fullContent: '', content: '', thinking: '', isThinking: false, duration: 0, error: null }); + + // 1. 先获取题目信息 + const questionRes = await fetch(`/api/projects/${projectId}/blind-test-tasks/${taskId}/question`, { + signal: controller.signal, + cache: 'no-store' + }); + + if (!questionRes.ok) throw new Error('获取题目失败'); + + const questionData = await questionRes.json(); + + if (questionData.completed) { + setCompleted(true); + return; + } + + setCurrentQuestion({ + id: questionData.questionId, + question: questionData.question, + answer: questionData.answer, + index: questionData.questionIndex, + total: questionData.totalQuestions + }); + setIsSwapped(questionData.isSwapped); + setCompleted(false); + + // 2. 并行调用两个模型的流式接口 + setStreamingA(true); + setStreamingB(true); + + const processStream = async (modelType, setAnswer, setStreaming) => { + const modelStartTime = Date.now(); + try { + const streamUrl = `/api/projects/${projectId}/blind-test-tasks/${taskId}/stream-model?model=${modelType}`; + const response = await fetch(streamUrl, { + signal: controller.signal + }); + + if (!response.ok) { + throw new Error(`模型${modelType}调用失败: ${response.status}`); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + + let fullContent = ''; + let currentContent = ''; + let currentThinking = ''; + let isInThinking = false; + let pendingBuffer = ''; // 用于处理跨 chunk 的标签识别 + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + const chunk = decoder.decode(value, { stream: true }); + pendingBuffer += chunk; + + // 处理缓冲区中的内容 + while (pendingBuffer.length > 0) { + // 如果正在思考中,寻找结束标签 + if (isInThinking) { + const endTagIndex = pendingBuffer.indexOf('
'); + if (endTagIndex !== -1) { + const thinkingPart = pendingBuffer.substring(0, endTagIndex); + currentThinking += thinkingPart; + fullContent += thinkingPart + '
'; + isInThinking = false; + pendingBuffer = pendingBuffer.substring(endTagIndex + 8); + continue; + } else { + // 没有找到结束标签,但可能缓冲区末尾包含了部分结束标签 + // 保留最后 7 个字符("
" 长度为 8)以防被截断 + const safeLength = Math.max(0, pendingBuffer.length - 7); + const processingPart = pendingBuffer.substring(0, safeLength); + currentThinking += processingPart; + fullContent += processingPart; + pendingBuffer = pendingBuffer.substring(safeLength); + break; // 等待下一个 chunk + } + } else { + // 不在思考中,寻找开始标签 + const startTagIndex = pendingBuffer.indexOf(''); + if (startTagIndex !== -1) { + const contentPart = pendingBuffer.substring(0, startTagIndex); + currentContent += contentPart; + fullContent += contentPart + ''; + isInThinking = true; + pendingBuffer = pendingBuffer.substring(startTagIndex + 7); + continue; + } else { + // 没有找到开始标签,保留最后 6 个字符以防开始标签被截断 + const safeLength = Math.max(0, pendingBuffer.length - 6); + const processingPart = pendingBuffer.substring(0, safeLength); + currentContent += processingPart; + fullContent += processingPart; + pendingBuffer = pendingBuffer.substring(safeLength); + break; // 等待下一个 chunk + } + } + } + + setAnswer(prev => ({ + ...prev, + fullContent, + content: currentContent, + thinking: currentThinking, + isThinking: isInThinking + })); + } + + const modelDuration = Date.now() - modelStartTime; + setAnswer(prev => ({ ...prev, duration: modelDuration })); + setStreaming(false); + } catch (err) { + if (err.name === 'AbortError') return; + console.error(`模型${modelType}错误:`, err); + const modelDuration = Date.now() - modelStartTime; + setAnswer(prev => ({ + ...prev, + error: err.message, + duration: modelDuration + })); + setStreaming(false); + } + }; + + // 根据是否交换决定左右对应的模型 + const leftModel = questionData.isSwapped ? 'B' : 'A'; + const rightModel = questionData.isSwapped ? 'A' : 'B'; + + await Promise.all([ + processStream(leftModel, setLeftAnswer, setStreamingA), + processStream(rightModel, setRightAnswer, setStreamingB) + ]); + } catch (err) { + if (err.name === 'AbortError') return; + console.error('获取题目失败:', err); + setError(err.message || '获取当前题目失败'); + setStreamingA(false); + setStreamingB(false); + } finally { + // 只有当前请求未被取消时才重置loading + if (abortControllerRef.current === controller) { + setAnswersLoading(false); + } + } + }, [projectId, taskId]); + + // 提交投票 + const submitVote = useCallback( + async vote => { + if (!projectId || !taskId || !currentQuestion) return { success: false }; + + try { + setVoting(true); + setError(''); + + const response = await fetch(`/api/projects/${projectId}/blind-test-tasks/${taskId}/vote`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + vote, + questionId: currentQuestion.id, + isSwapped, + // 使用 fullContent 提交,包含思考过程 + leftAnswer: leftAnswer?.fullContent || leftAnswer?.content || '', + rightAnswer: rightAnswer?.fullContent || rightAnswer?.content || '' + }) + }); + + const result = await response.json(); + + if (result.code === 0) { + // 等待任务状态更新(进度条) + await loadTask(true); + + if (result.data.isCompleted) { + setCompleted(true); + } else { + // 获取下一题 + await fetchCurrentQuestion(); + } + return { success: true, data: result.data }; + } else { + setError(result.error || '提交投票失败'); + return { success: false, error: result.error }; + } + } catch (err) { + console.error('提交投票失败:', err); + setError('提交投票失败'); + return { success: false, error: '提交投票失败' }; + } finally { + setVoting(false); + } + }, + [projectId, taskId, currentQuestion, isSwapped, leftAnswer, rightAnswer, loadTask, fetchCurrentQuestion] + ); + + // 中断任务 + const interruptTask = useCallback(async () => { + if (!projectId || !taskId) return false; + + try { + const response = await fetch(`/api/projects/${projectId}/blind-test-tasks/${taskId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'interrupt' }) + }); + + const result = await response.json(); + + if (result.code === 0) { + setCompleted(true); + loadTask(); + return true; + } else { + setError(result.error || '中断任务失败'); + return false; + } + } catch (err) { + console.error('中断任务失败:', err); + setError('中断任务失败'); + return false; + } + }, [projectId, taskId, loadTask]); + + // 初始加载 + useEffect(() => { + loadTask(); + }, [loadTask]); + + // 任务加载完成后,如果任务进行中,自动获取当前题目(只执行一次) + useEffect(() => { + if (task && task.status === 0 && !completed && !hasAutoLoadedRef.current && projectId && taskId) { + hasAutoLoadedRef.current = true; + fetchCurrentQuestion(); + } + }, [task, completed, projectId, taskId, fetchCurrentQuestion]); + + // 计算结果统计 + const getResultStats = useCallback(() => { + if (!task?.detail?.results) return null; + + const results = task.detail.results; + const totalModelAScore = results.reduce((sum, r) => sum + (r.modelAScore || 0), 0); + const totalModelBScore = results.reduce((sum, r) => sum + (r.modelBScore || 0), 0); + + const leftWins = results.filter(r => r.vote === 'left').length; + const rightWins = results.filter(r => r.vote === 'right').length; + const bothGood = results.filter(r => r.vote === 'both_good').length; + const bothBad = results.filter(r => r.vote === 'both_bad').length; + + // 计算实际模型胜出次数(需要考虑 swap) + const modelAWins = results.filter(r => { + if (r.vote === 'left' && !r.isSwapped) return true; + if (r.vote === 'right' && r.isSwapped) return true; + return false; + }).length; + + const modelBWins = results.filter(r => { + if (r.vote === 'left' && r.isSwapped) return true; + if (r.vote === 'right' && !r.isSwapped) return true; + return false; + }).length; + + return { + totalQuestions: results.length, + modelAScore: totalModelAScore, + modelBScore: totalModelBScore, + modelAWins, + modelBWins, + ties: bothGood + bothBad, + bothGood, + bothBad, + leftWins, + rightWins + }; + }, [task]); + + // 组件卸载时取消请求 + useEffect(() => { + return () => { + if (abortControllerRef.current) { + abortControllerRef.current.abort(); + } + }; + }, []); + + return { + // 任务详情 + task, + loading, + error, + setError, + loadTask, + + // 当前题目状态 + currentQuestion, + leftAnswer, + rightAnswer, + answersLoading, + + // 流式状态 + streamingA, + streamingB, + + // 投票状态 + voting, + completed, + + // 操作 + fetchCurrentQuestion, + submitVote, + interruptTask, + + // 结果统计 + getResultStats + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestTasks.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestTasks.js new file mode 100644 index 0000000..2ca159d --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/hooks/useBlindTestTasks.js @@ -0,0 +1,140 @@ +'use client'; + +import { useState, useCallback, useEffect } from 'react'; + +/** + * 盲测任务列表管理 Hook + */ +export default function useBlindTestTasks(projectId) { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(6); + const [total, setTotal] = useState(0); + + // 加载任务列表 + const loadTasks = useCallback( + async (isRefresh = false) => { + if (!projectId) return; + + try { + if (!isRefresh) setLoading(true); + setError(''); + const response = await fetch(`/api/projects/${projectId}/blind-test-tasks?page=${page}&pageSize=${pageSize}`); + const result = await response.json(); + + if (result.code === 0) { + setTasks(result.data.items || []); + setTotal(result.data.total || 0); + } else { + setError(result.error || '加载失败'); + } + } catch (err) { + console.error('加载盲测任务失败:', err); + setError('加载失败'); + } finally { + if (!isRefresh) setLoading(false); + } + }, + [projectId, page, pageSize] + ); + + // 初始加载和分页变化加载 + useEffect(() => { + loadTasks(); + }, [loadTasks]); + + // 删除任务 + const deleteTask = useCallback( + async taskId => { + try { + const response = await fetch(`/api/projects/${projectId}/blind-test-tasks/${taskId}`, { + method: 'DELETE' + }); + const result = await response.json(); + + if (result.code === 0) { + loadTasks(); + return true; + } else { + setError(result.error || '删除失败'); + return false; + } + } catch (err) { + console.error('删除任务失败:', err); + setError('删除失败'); + return false; + } + }, + [projectId, loadTasks] + ); + + // 中断任务 + const interruptTask = useCallback( + async taskId => { + try { + const response = await fetch(`/api/projects/${projectId}/blind-test-tasks/${taskId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'interrupt' }) + }); + const result = await response.json(); + + if (result.code === 0) { + loadTasks(); + return true; + } else { + setError(result.error || '中断失败'); + return false; + } + } catch (err) { + console.error('中断任务失败:', err); + setError('中断失败'); + return false; + } + }, + [projectId, loadTasks] + ); + + // 创建任务 + const createTask = useCallback( + async taskData => { + try { + const response = await fetch(`/api/projects/${projectId}/blind-test-tasks`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(taskData) + }); + const result = await response.json(); + + if (result.code === 0) { + loadTasks(); + return { success: true, data: result.data }; + } else { + return { success: false, error: result.error || '创建失败' }; + } + } catch (err) { + console.error('创建任务失败:', err); + return { success: false, error: '创建失败' }; + } + }, + [projectId, loadTasks] + ); + + return { + tasks, + loading, + error, + setError, + loadTasks, + deleteTask, + interruptTask, + createTask, + page, + setPage, + pageSize, + setPageSize, + total + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/page.js b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/page.js new file mode 100644 index 0000000..7fa7d9a --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/blind-test-tasks/page.js @@ -0,0 +1,215 @@ +'use client'; + +import { useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { + Box, + Container, + Typography, + Button, + Grid, + CircularProgress, + Alert, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + TablePagination +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import CompareArrowsIcon from '@mui/icons-material/CompareArrows'; +import { useTranslation } from 'react-i18next'; + +import useBlindTestTasks from './hooks/useBlindTestTasks'; +import BlindTestTaskCard from './components/BlindTestTaskCard'; +import CreateBlindTestDialog from './components/CreateBlindTestDialog'; + +export default function BlindTestTasksPage() { + const { projectId } = useParams(); + const router = useRouter(); + const { t } = useTranslation(); + + const { + tasks, + loading, + error, + setError, + deleteTask, + interruptTask, + createTask, + page, + setPage, + pageSize, + setPageSize, + total + } = useBlindTestTasks(projectId); + + const [createDialogOpen, setCreateDialogOpen] = useState(false); + const [deleteDialog, setDeleteDialog] = useState({ open: false, task: null }); + const [interruptDialog, setInterruptDialog] = useState({ open: false, task: null }); + + const handleView = task => router.push(`/projects/${projectId}/blind-test-tasks/${task.id}`); + const handleContinue = task => router.push(`/projects/${projectId}/blind-test-tasks/${task.id}`); + const handleDelete = task => setDeleteDialog({ open: true, task }); + const handleInterrupt = task => setInterruptDialog({ open: true, task }); + + const handlePageChange = (event, newPage) => { + setPage(newPage + 1); + }; + + const handlePageSizeChange = event => { + setPageSize(parseInt(event.target.value, 10)); + setPage(1); + }; + + const confirmDelete = async () => { + if (deleteDialog.task) { + await deleteTask(deleteDialog.task.id); + } + setDeleteDialog({ open: false, task: null }); + }; + + const confirmInterrupt = async () => { + if (interruptDialog.task) { + await interruptTask(interruptDialog.task.id); + } + setInterruptDialog({ open: false, task: null }); + }; + + const handleCreate = async taskData => { + const result = await createTask(taskData); + if (result.success) { + // 创建成功后跳转到任务详情页开始盲测 + router.push(`/projects/${projectId}/blind-test-tasks/${result.data.id}`); + } + return result; + }; + + return ( + + {/* 页面标题 */} + + + + + {t('blindTest.title', '人工盲测任务')} + + + + + + {/* 错误提示 */} + {error && ( + setError('')}> + {error} + + )} + + {/* 加载状态 */} + {loading && ( + + + + )} + + {/* 空状态 */} + {!loading && tasks.length === 0 && ( + + + + {t('blindTest.noTasks', '暂无盲测任务')} + + + {t('blindTest.noTasksHint', '创建盲测任务来对比两个模型的回答质量')} + + + + )} + + {/* 任务列表 */} + {!loading && tasks.length > 0 && ( + <> + + {tasks.map(task => ( + + + + ))} + + + + + + )} + + {/* 创建对话框 */} + setCreateDialogOpen(false)} + projectId={projectId} + onCreate={handleCreate} + /> + + {/* 删除确认对话框 */} + setDeleteDialog({ open: false, task: null })}> + {t('blindTest.deleteConfirmTitle', '确认删除')} + + + {t('blindTest.deleteConfirmMessage', '确定要删除这个盲测任务吗?此操作不可撤销。')} + + + + + + + + + {/* 中断确认对话框 */} + setInterruptDialog({ open: false, task: null })}> + {t('blindTest.interruptConfirmTitle', '确认中断')} + + + {t('blindTest.interruptConfirmMessage', '确定要中断这个盲测任务吗?已完成的评判结果将保留。')} + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/[datasetId]/page.js b/easy-dataset-main/app/projects/[projectId]/datasets/[datasetId]/page.js new file mode 100644 index 0000000..893becd --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/[datasetId]/page.js @@ -0,0 +1,202 @@ +'use client'; + +import { Container, Box, Typography, Alert, Snackbar, Paper } from '@mui/material'; +import { useEffect } from 'react'; +import ChunkViewDialog from '@/components/text-split/ChunkViewDialog'; +import DatasetHeader from '@/components/datasets/DatasetHeader'; +import DatasetMetadata from '@/components/datasets/DatasetMetadata'; +import EditableField from '@/components/datasets/EditableField'; +import OptimizeDialog from '@/components/datasets/OptimizeDialog'; +import DatasetRatingSection from '@/components/datasets/DatasetRatingSection'; +import useDatasetDetails from '@/app/projects/[projectId]/datasets/[datasetId]/useDatasetDetails'; +import { useTranslation } from 'react-i18next'; + +/** + * 数据集详情页面 + */ +export default function DatasetDetailsPage({ params }) { + const { projectId, datasetId } = params; + + const { t } = useTranslation(); + // 使用自定义Hook管理状态和逻辑 + const { + currentDataset, + loading, + editingAnswer, + editingCot, + editingQuestion, + answerValue, + cotValue, + questionValue, + snackbar, + confirming, + unconfirming, + optimizeDialog, + viewDialogOpen, + viewChunk, + datasetsAllCount, + datasetsConfirmCount, + answerTokens, + cotTokens, + shortcutsEnabled, + setShortcutsEnabled, + setSnackbar, + setAnswerValue, + setCotValue, + setQuestionValue, + setEditingAnswer, + setEditingCot, + setEditingQuestion, + handleNavigate, + handleConfirm, + handleUnconfirm, + handleSave, + handleDelete, + handleOpenOptimizeDialog, + handleCloseOptimizeDialog, + handleOptimize, + handleViewChunk, + handleCloseViewDialog + } = useDatasetDetails(projectId, datasetId); + + // 加载状态 + if (loading) { + return ( + + + {t('datasets.loadingDataset')} + + + ); + } + + // 无数据状态 + if (!currentDataset) { + return ( + + {t('datasets.datasetNotFound')} + + ); + } + + return ( + + {/* 顶部导航栏 */} + + + {/* 主要布局:左右分栏 */} + + {/* 左侧主要内容区域 */} + + + setEditingQuestion(true)} + onChange={e => setQuestionValue(e.target.value)} + onSave={() => handleSave('question', questionValue)} + dataset={currentDataset} + onCancel={() => { + setEditingQuestion(false); + setQuestionValue(currentDataset.question); + }} + /> + + setEditingAnswer(true)} + onChange={e => setAnswerValue(e.target.value)} + onSave={() => handleSave('answer', answerValue)} + onCancel={() => { + setEditingAnswer(false); + setAnswerValue(currentDataset.answer); + }} + dataset={currentDataset} + onOptimize={handleOpenOptimizeDialog} + tokenCount={answerTokens} + optimizing={optimizeDialog.loading} + /> + + setEditingCot(true)} + onChange={e => setCotValue(e.target.value)} + onSave={() => handleSave('cot', cotValue)} + dataset={currentDataset} + onCancel={() => { + setEditingCot(false); + setCotValue(currentDataset.cot || ''); + }} + tokenCount={cotTokens} + /> + + + + {/* 右侧固定侧边栏 */} + + {/* 数据集元数据信息 */} + + + {/* 评分、标签、备注区域 */} + { + // 更新成功后刷新数据,保持页面状态同步 + // 这里可以调用 useDatasetDetails 的刷新逻辑 + }} + currentDataset={currentDataset} + /> + + + + {/* 消息提示 */} + setSnackbar(prev => ({ ...prev, open: false }))} + anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} + > + setSnackbar(prev => ({ ...prev, open: false }))} + severity={snackbar.severity} + sx={{ width: '100%' }} + > + {snackbar.message} + + + + {/* AI优化对话框 */} + + + {/* 文本块详情对话框 */} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/[datasetId]/useDatasetDetails.js b/easy-dataset-main/app/projects/[projectId]/datasets/[datasetId]/useDatasetDetails.js new file mode 100644 index 0000000..1e2ed89 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/[datasetId]/useDatasetDetails.js @@ -0,0 +1,471 @@ +'use client'; + +import { useState, useEffect, useRef, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import { useAtomValue } from 'jotai/index'; +import { selectedModelInfoAtom } from '@/lib/store'; +import axios from 'axios'; +import { toast } from 'sonner'; +import i18n from '@/lib/i18n'; + +/** + * 数据集详情页面业务逻辑 Hook + */ +export default function useDatasetDetails(projectId, datasetId) { + const router = useRouter(); + const [datasets, setDatasets] = useState([]); + const [currentDataset, setCurrentDataset] = useState(null); + const [loading, setLoading] = useState(true); + const [editingAnswer, setEditingAnswer] = useState(false); + const [editingCot, setEditingCot] = useState(false); + const [editingQuestion, setEditingQuestion] = useState(false); + const [answerValue, setAnswerValue] = useState(''); + const [cotValue, setCotValue] = useState(''); + const [questionValue, setQuestionValue] = useState(''); + const [snackbar, setSnackbar] = useState({ + open: false, + message: '', + severity: 'success' + }); + const [confirming, setConfirming] = useState(false); + const [unconfirming, setUnconfirming] = useState(false); + const [optimizeDialog, setOptimizeDialog] = useState({ + open: false, + loading: false + }); + const [viewDialogOpen, setViewDialogOpen] = useState(false); + const [viewChunk, setViewChunk] = useState(null); + const [datasetsAllCount, setDatasetsAllCount] = useState(0); + const [datasetsConfirmCount, setDatasetsConfirmCount] = useState(0); + const [answerTokens, setAnswerTokens] = useState(0); + const [cotTokens, setCotTokens] = useState(0); + const model = useAtomValue(selectedModelInfoAtom); + const [shortcutsEnabled, setShortcutsEnabled] = useState(() => { + const storedValue = localStorage.getItem('shortcutsEnabled'); + return storedValue !== null ? storedValue === 'true' : false; + }); + + // 输入环境判断,避免在输入框/可编辑区域误触快捷键 + const isEditableTarget = el => { + if (!el) return false; + const tag = el.tagName?.toLowerCase(); + if (tag && ['input', 'textarea', 'select'].includes(tag)) return true; + if (el.isContentEditable) return true; + // 兼容嵌套的可编辑区域与常见富文本编辑器 + return !!el.closest?.('[contenteditable="true"], .ProseMirror, .ql-editor'); + }; + + // 简单节流,避免连续触发 + const lastShortcutRef = useRef(0); + + // 异步获取Token数量 + const fetchTokenCount = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/datasets/${datasetId}/token-count`); + if (response.ok) { + const data = await response.json(); + if (data.answerTokens !== undefined) { + setAnswerTokens(data.answerTokens); + } + if (data.cotTokens !== undefined) { + setCotTokens(data.cotTokens); + } + } + } catch (error) { + console.error('获取Token数量失败:', error); + // Token加载失败不阻塞主界面或显示错误提示 + } + }; + + // 获取数据集详情 + const fetchDatasets = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/datasets/${datasetId}`); + if (!response.ok) throw new Error('获取数据集详情失败'); + const data = await response.json(); + setCurrentDataset(data.datasets); + setCotValue(data.datasets?.cot); + setAnswerValue(data.datasets?.answer); + setQuestionValue(data.datasets?.question); + setDatasetsAllCount(data.total); + setDatasetsConfirmCount(data.confirmedCount); + + // 数据加载完成后,异步获取Token数量 + fetchTokenCount(); + } catch (error) { + setSnackbar({ + open: true, + message: error.message, + severity: 'error' + }); + } finally { + setLoading(false); + } + }; + + // 确认并保存数据集 + const handleConfirm = async () => { + try { + setConfirming(true); + const response = await fetch(`/api/projects/${projectId}/datasets?id=${datasetId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + confirmed: true + }) + }); + + if (!response.ok) { + throw new Error('操作失败'); + } + + setCurrentDataset(prev => ({ ...prev, confirmed: true })); + + setSnackbar({ + open: true, + message: '操作成功', + severity: 'success' + }); + + // 导航到下一个数据集 + handleNavigate('next'); + } catch (error) { + setSnackbar({ + open: true, + message: error.message || '操作失败', + severity: 'error' + }); + } finally { + setConfirming(false); + } + }; + + // 取消确认数据集 + const handleUnconfirm = async () => { + try { + setUnconfirming(true); + const response = await fetch(`/api/projects/${projectId}/datasets?id=${datasetId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + confirmed: false + }) + }); + + if (!response.ok) { + throw new Error('操作失败'); + } + + setCurrentDataset(prev => ({ ...prev, confirmed: false })); + + setSnackbar({ + open: true, + message: '已取消确认', + severity: 'success' + }); + } catch (error) { + setSnackbar({ + open: true, + message: error.message || '取消确认失败', + severity: 'error' + }); + } finally { + setUnconfirming(false); + } + }; + + // 导航到其他数据集 + const handleNavigate = async direction => { + const response = await axios.get(`/api/projects/${projectId}/datasets/${datasetId}?operateType=${direction}`); + if (response.data) { + router.push(`/projects/${projectId}/datasets/${response.data.id}`); + } else { + toast.warning(`已经是${direction === 'next' ? '最后' : '第'}一条数据了`); + } + }; + + // 保存编辑 + const handleSave = async (field, value) => { + try { + const response = await fetch(`/api/projects/${projectId}/datasets?id=${datasetId}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + [field]: value + }) + }); + + if (!response.ok) { + throw new Error('保存失败'); + } + + const data = await response.json(); + setCurrentDataset(prev => ({ ...prev, [field]: value })); + + setSnackbar({ + open: true, + message: '保存成功', + severity: 'success' + }); + + // 重置编辑状态 + if (field === 'answer') setEditingAnswer(false); + if (field === 'cot') setEditingCot(false); + if (field === 'question') setEditingQuestion(false); + } catch (error) { + setSnackbar({ + open: true, + message: error.message || '保存失败', + severity: 'error' + }); + } + }; + + // 删除数据集 + const handleDelete = async () => { + if (!confirm('确定要删除这条数据吗?此操作不可撤销。')) return; + + try { + // 尝试获取下一个数据集,在删除前先确保有可导航的目标 + const nextResponse = await axios.get(`/api/projects/${projectId}/datasets/${datasetId}?operateType=next`); + const hasNextDataset = !!nextResponse.data; + const nextDatasetId = hasNextDataset ? nextResponse.data.id : null; + + // 删除当前数据集 + const deleteResponse = await fetch(`/api/projects/${projectId}/datasets?id=${datasetId}`, { + method: 'DELETE' + }); + + if (!deleteResponse.ok) { + throw new Error('删除失败'); + } + + // 导航逻辑:有下一个就跳转下一个,没有则返回列表页 + if (hasNextDataset) { + router.push(`/projects/${projectId}/datasets/${nextDatasetId}`); + } else { + // 没有更多数据集,返回列表页面 + router.push(`/projects/${projectId}/datasets`); + } + + toast.success('删除成功'); + } catch (error) { + setSnackbar({ + open: true, + message: error.message || '删除失败', + severity: 'error' + }); + } + }; + + // 优化对话框相关操作 + const handleOpenOptimizeDialog = () => { + setOptimizeDialog({ + open: true, + loading: false + }); + }; + + const handleCloseOptimizeDialog = () => { + setOptimizeDialog(prev => { + // 如果正在优化,不允许关闭 + if (prev.loading) { + return prev; + } + return { + open: false, + loading: false + }; + }); + }; + + // 优化操作 + const handleOptimize = async advice => { + if (!model) { + setSnackbar({ + open: true, + message: '请先选择模型,可以在顶部导航栏选择', + severity: 'error' + }); + return; + } + + // 立即关闭对话框,并设置优化中状态 + setOptimizeDialog(prev => { + const newState = { + open: false, + loading: true + }; + return newState; + }); + + toast.info('已开始优化,请稍候...'); + + // 异步后台处理,不等待结果 + (async () => { + try { + const language = i18n.language === 'zh-CN' ? '中文' : 'en'; + const response = await fetch(`/api/projects/${projectId}/datasets/optimize`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + datasetId, + model, + advice, + language + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || '优化失败'); + } + + // 优化成功后,重新查询数据以获取最新状态 + await fetchDatasets(); + // 优化可能改变了文本内容,重新获取Token计数 + fetchTokenCount(); + + toast.success('AI智能优化成功'); + } catch (error) { + toast.error(error.message); + } finally { + setOptimizeDialog({ + open: false, + loading: false + }); + } + })(); + }; + + // 查看文本块详情 + const handleViewChunk = async chunkContent => { + try { + setViewChunk(chunkContent); + setViewDialogOpen(true); + } catch (error) { + console.error('查看文本块出错', error); + setSnackbar({ + open: true, + message: error.message, + severity: 'error' + }); + setViewDialogOpen(false); + } + }; + + // 关闭文本块详情对话框 + const handleCloseViewDialog = () => { + setViewDialogOpen(false); + }; + + // 初始化和快捷键事件 + useEffect(() => { + fetchDatasets(); + }, [projectId, datasetId]); + + // 快捷键状态变化 + useEffect(() => { + localStorage.setItem('shortcutsEnabled', shortcutsEnabled); + }, [shortcutsEnabled]); + + // 监听键盘事件 + useEffect(() => { + const handleKeyDown = event => { + if (!shortcutsEnabled) return; + + // 在输入框或可编辑区域时不触发 + const activeEl = typeof document !== 'undefined' ? document.activeElement : null; + if (isEditableTarget(event.target) || isEditableTarget(activeEl)) { + return; + } + + // 仅要求 Shift 修饰键,降低误触且更简单 + if (!event.shiftKey) return; + + // 简单节流,过滤极短时间内重复触发 + const now = Date.now(); + if (now - (lastShortcutRef.current || 0) < 250) { + return; + } + lastShortcutRef.current = now; + + switch (event.key) { + case 'ArrowLeft': // 上一个(Shift + ArrowLeft) + event.preventDefault(); + handleNavigate('prev'); + break; + case 'ArrowRight': // 下一个(Shift + ArrowRight) + event.preventDefault(); + handleNavigate('next'); + break; + case 'y': // 确认(Shift + Y) + case 'Y': + if (!confirming && currentDataset && !currentDataset.confirmed) { + event.preventDefault(); + handleConfirm(); + } + break; + case 'd': // 删除(Shift + D) + case 'D': + event.preventDefault(); + handleDelete(); + break; + default: + break; + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [shortcutsEnabled, confirming, currentDataset]); + + return { + loading, + currentDataset, + answerValue, + cotValue, + questionValue, + editingAnswer, + editingCot, + editingQuestion, + confirming, + unconfirming, + snackbar, + optimizeDialog, + viewDialogOpen, + viewChunk, + datasetsAllCount, + datasetsConfirmCount, + answerTokens, + cotTokens, + shortcutsEnabled, + setShortcutsEnabled, + setSnackbar, + setAnswerValue, + setCotValue, + setQuestionValue, + setEditingAnswer, + setEditingCot, + setEditingQuestion, + handleNavigate, + handleConfirm, + handleUnconfirm, + handleSave, + handleDelete, + handleOpenOptimizeDialog, + handleCloseOptimizeDialog, + handleOptimize, + handleViewChunk, + handleCloseViewDialog + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/components/ActionBar.js b/easy-dataset-main/app/projects/[projectId]/datasets/components/ActionBar.js new file mode 100644 index 0000000..7209eaf --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/components/ActionBar.js @@ -0,0 +1,33 @@ +'use client'; + +import { Box, Button } from '@mui/material'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import FileUploadIcon from '@mui/icons-material/FileUpload'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import { useTranslation } from 'react-i18next'; + +const ActionBar = ({ onBatchEvaluate, onImport, onExport, batchEvaluating = false }) => { + const { t } = useTranslation(); + + return ( + + + + + + ); +}; + +export default ActionBar; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/components/DatasetList.js b/easy-dataset-main/app/projects/[projectId]/datasets/components/DatasetList.js new file mode 100644 index 0000000..915b60d --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/components/DatasetList.js @@ -0,0 +1,422 @@ +'use client'; + +import { + Box, + Typography, + IconButton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Chip, + Divider, + useTheme, + alpha, + Tooltip, + Checkbox, + TablePagination, + TextField, + Card, + CircularProgress +} from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import StarIcon from '@mui/icons-material/Star'; +import { useTranslation } from 'react-i18next'; +import { getRatingConfigI18n, formatScore } from '@/components/datasets/utils/ratingUtils'; + +// 数据集列表组件 +const DatasetList = ({ + datasets, + onViewDetails, + onDelete, + onEvaluate, + page, + rowsPerPage, + onPageChange, + onRowsPerPageChange, + total, + selectedIds, + onSelectAll, + onSelectItem, + evaluatingIds = [], + loading = false +}) => { + const theme = useTheme(); + const { t } = useTranslation(); + + const bgColor = theme.palette.mode === 'dark' ? theme.palette.primary.dark : theme.palette.primary.light; + const color = + theme.palette.mode === 'dark' + ? theme.palette.getContrastText(theme.palette.primary.main) + : theme.palette.getContrastText(theme.palette.primary.contrastText); + + const RatingChip = ({ score }) => { + const config = getRatingConfigI18n(score, t); + return ( + } + label={`${formatScore(score)} ${config.label}`} + size="small" + sx={{ + backgroundColor: config.backgroundColor, + color: config.color, + fontWeight: 'medium', + '& .MuiChip-icon': { + color: config.color + } + }} + /> + ); + }; + + return ( + + + + + + + + 0 && selectedIds.length < total} + checked={total > 0 && selectedIds.length === total} + onChange={onSelectAll} + /> + + + {t('datasets.question')} + + + {t('datasets.rating', '评分')} + + + {t('datasets.model')} + + + {t('datasets.domainTag')} + + + {t('datasets.createdAt')} + + + {t('common.actions')} + + + + + {datasets.map((dataset, index) => ( + <> + onViewDetails(dataset.id)} + > + + { + e.stopPropagation(); + onSelectItem(dataset.id); + }} + onClick={e => e.stopPropagation()} + /> + + + + + {dataset.question} + + {dataset.confirmed && ( + + )} + + + + + + + + + + {dataset.questionLabel ? ( + + ) : ( + + {t('datasets.noTag')} + + )} + + + + {new Date(dataset.createAt).toLocaleDateString('zh-CN')} + + + + + + { + e.stopPropagation(); + onViewDetails(dataset.id); + }} + sx={{ + color: theme.palette.primary.main, + '&:hover': { backgroundColor: alpha(theme.palette.primary.main, 0.1) } + }} + > + + + + + { + e.stopPropagation(); + onEvaluate && onEvaluate(dataset); + }} + sx={{ + color: theme.palette.secondary.main, + '&:hover': { backgroundColor: alpha(theme.palette.secondary.main, 0.1) } + }} + > + {evaluatingIds.includes(dataset.id) ? ( + + ) : ( + + )} + + + + { + e.stopPropagation(); + onDelete(dataset); + }} + sx={{ + color: theme.palette.error.main, + '&:hover': { backgroundColor: alpha(theme.palette.error.main, 0.1) } + }} + > + + + + + + + + ))} + {datasets.length === 0 && ( + + + + {t('datasets.noData')} + + + + )} + +
+
+ {loading && ( + + + + {t('datasets.loading')} + + + )} +
+ + + t('datasets.pagination', { from, to, count })} + sx={{ + '.MuiTablePagination-selectLabel, .MuiTablePagination-displayedRows': { + fontWeight: 'medium' + }, + border: 'none' + }} + /> + + {t('common.jumpTo')}: + { + if (e.key === 'Enter') { + const pageNum = parseInt(e.target.value, 10); + if (pageNum >= 1 && pageNum <= Math.ceil(total / rowsPerPage)) { + onPageChange(null, pageNum - 1); + e.target.value = ''; + } + } + }} + /> + + +
+ ); +}; + +export default DatasetList; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/components/DeleteConfirmDialog.js b/easy-dataset-main/app/projects/[projectId]/datasets/components/DeleteConfirmDialog.js new file mode 100644 index 0000000..e392615 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/components/DeleteConfirmDialog.js @@ -0,0 +1,105 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Typography, + Paper, + Box, + LinearProgress, + Button, + useTheme, + alpha +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +const DeleteConfirmDialog = ({ open, datasets, onClose, onConfirm, batch, progress, deleting }) => { + const theme = useTheme(); + const { t } = useTranslation(); + const dataset = datasets?.[0]; + + return ( + + + + {t('common.confirmDelete')} + + + + + {batch + ? t('datasets.batchconfirmDeleteMessage', { + count: datasets.length + }) + : t('common.confirmDeleteDataSet')} + + {batch ? ( + '' + ) : ( + + + {t('datasets.question')}: + + {dataset?.question} + + )} + {deleting && progress ? ( + + + + {progress.percentage}% + + + + + + + {t('datasets.deletingProgress', '正在删除 {{completed}}/{{total}} 个数据集...', { + completed: progress.completed, + total: progress.total + })} + + + ) : null} + + + + + + + ); +}; + +export default DeleteConfirmDialog; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/components/FilterDialog.js b/easy-dataset-main/app/projects/[projectId]/datasets/components/FilterDialog.js new file mode 100644 index 0000000..1158431 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/components/FilterDialog.js @@ -0,0 +1,198 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + Typography, + Select, + MenuItem, + Slider, + TextField, + Button, + InputAdornment +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import { useTranslation } from 'react-i18next'; + +const FilterDialog = ({ + open, + onClose, + filterConfirmed, + filterHasCot, + filterIsDistill, + filterScoreRange, + filterCustomTag, + filterNoteKeyword, + filterChunkName, + availableTags, + onFilterConfirmedChange, + onFilterHasCotChange, + onFilterIsDistillChange, + onFilterScoreRangeChange, + onFilterCustomTagChange, + onFilterNoteKeywordChange, + onFilterChunkNameChange, + onResetFilters, + onApplyFilters +}) => { + const { t } = useTranslation(); + + return ( + + {t('datasets.filtersTitle')} + + + + {t('datasets.filterConfirmationStatus')} + + + + + + + {t('datasets.filterCotStatus')} + + + + + + + {t('datasets.filterDistill')} + + + + + + + {t('datasets.filterScoreRange')} + + + onFilterScoreRangeChange(newValue)} + valueLabelDisplay="auto" + min={0} + max={5} + step={0.5} + marks={[ + { value: 0, label: '0' }, + { value: 2.5, label: '2.5' }, + { value: 5, label: '5' } + ]} + sx={{ mt: 1 }} + /> + + {t('datasets.scoreRange', '{{min}} - {{max}} 分', { + min: filterScoreRange[0], + max: filterScoreRange[1] + })} + + + + + + + {t('datasets.filterCustomTag')} + + + + + + + {t('datasets.filterNoteKeyword')} + + onFilterNoteKeywordChange(e.target.value)} + placeholder={t('datasets.filterNoteKeywordPlaceholder')} + fullWidth + size="small" + sx={{ mt: 1 }} + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + + + + {t('datasets.filterChunkName')} + + onFilterChunkNameChange(e.target.value)} + placeholder={t('datasets.filterChunkNamePlaceholder')} + fullWidth + size="small" + sx={{ mt: 1 }} + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + + + + + + + ); +}; + +export default FilterDialog; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/components/SearchBar.js b/easy-dataset-main/app/projects/[projectId]/datasets/components/SearchBar.js new file mode 100644 index 0000000..b111e94 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/components/SearchBar.js @@ -0,0 +1,68 @@ +'use client'; + +import { Box, Paper, IconButton, InputBase, Select, MenuItem, Button, Badge } from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import { useTranslation } from 'react-i18next'; + +const SearchBar = ({ + searchQuery, + searchField, + onSearchQueryChange, + onSearchFieldChange, + onMoreFiltersClick, + activeFilterCount = 0 +}) => { + const { t } = useTranslation(); + + return ( + + + + + + onSearchQueryChange(e.target.value)} + endAdornment={ + + } + /> + + + + + + ); +}; + +export default SearchBar; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetEvaluation.js b/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetEvaluation.js new file mode 100644 index 0000000..5ae655c --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetEvaluation.js @@ -0,0 +1,165 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; + +/** + * 数据集评估相关的自定义 Hook + * 封装单个评估和批量评估的逻辑 + */ +const useDatasetEvaluation = (projectId, onEvaluationComplete) => { + const router = useRouter(); + const { t } = useTranslation(); + const model = useAtomValue(selectedModelInfoAtom); + + // 评估状态管理 + const [evaluatingIds, setEvaluatingIds] = useState([]); + const [batchEvaluating, setBatchEvaluating] = useState(false); + + /** + * 检查模型是否已配置 + */ + const checkModelConfiguration = () => { + if (!model || !model.modelName) { + toast.error(t('datasets.selectModelFirst', '请先选择模型')); + return false; + } + return true; + }; + + /** + * 处理单个数据集评估 + * @param {Object} dataset - 要评估的数据集对象 + */ + const handleEvaluateDataset = async dataset => { + // 检查模型配置 + if (!checkModelConfiguration()) { + return; + } + + try { + // 添加到评估中的ID列表 + setEvaluatingIds(prev => [...prev, dataset.id]); + + // 调用评估接口 + const evaluateResponse = await fetch(`/api/projects/${projectId}/datasets/${dataset.id}/evaluate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model, + language: 'zh-CN' + }) + }); + + const result = await evaluateResponse.json(); + + if (result.success) { + toast.success( + t('datasets.evaluateSuccess', '评估完成!评分:{{score}}/5', { + score: result.data.score + }) + ); + + // 调用回调函数通知评估完成(通常用于刷新数据列表) + if (onEvaluationComplete) { + await onEvaluationComplete(); + } + } else { + toast.error(result.message || t('datasets.evaluateFailed', '评估失败')); + } + } catch (error) { + console.error('评估失败:', error); + toast.error( + t('datasets.evaluateError', '评估失败: {{error}}', { + error: error.message + }) + ); + } finally { + // 从评估中的ID列表移除 + setEvaluatingIds(prev => prev.filter(id => id !== dataset.id)); + } + }; + + /** + * 处理批量评估 + */ + const handleBatchEvaluate = async () => { + // 检查模型配置 + if (!checkModelConfiguration()) { + return; + } + + try { + setBatchEvaluating(true); + + // 调用批量评估接口 + const response = await fetch(`/api/projects/${projectId}/datasets/batch-evaluate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model, + language: 'zh-CN' + }) + }); + + const result = await response.json(); + + if (result.success) { + toast.success(t('datasets.batchEvaluateStarted', '批量评估任务已启动,将在后台进行处理')); + // 跳转到任务页面查看进度 + router.push(`/projects/${projectId}/tasks`); + } else { + toast.error(result.message || t('datasets.batchEvaluateStartFailed', '启动批量评估失败')); + } + } catch (error) { + console.error('批量评估失败:', error); + toast.error( + t('datasets.batchEvaluateFailed', '批量评估失败: {{error}}', { + error: error.message + }) + ); + } finally { + setBatchEvaluating(false); + } + }; + + /** + * 检查指定数据集是否正在评估中 + * @param {string} datasetId - 数据集ID + * @returns {boolean} 是否正在评估中 + */ + const isEvaluating = datasetId => { + return evaluatingIds.includes(datasetId); + }; + + /** + * 获取当前正在评估的数据集数量 + * @returns {number} 正在评估的数据集数量 + */ + const getEvaluatingCount = () => { + return evaluatingIds.length; + }; + + return { + // 状态 + evaluatingIds, + batchEvaluating, + + // 方法 + handleEvaluateDataset, + handleBatchEvaluate, + + // 工具方法 + isEvaluating, + getEvaluatingCount, + + // 模型信息(便于组件使用) + model + }; +}; + +export default useDatasetEvaluation; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetExport.js b/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetExport.js new file mode 100644 index 0000000..cdb4874 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetExport.js @@ -0,0 +1,487 @@ +'use client'; + +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import axios from 'axios'; + +const useDatasetExport = projectId => { + const { t } = useTranslation(); + + // 优化的流式导出 - 使用 WritableStream 避免内存溢出 + const exportDatasetsStreaming = async (exportOptions, onProgress) => { + try { + const batchSize = exportOptions.batchSize || 1000; + let offset = 0; + let hasMore = true; + let totalProcessed = 0; + let isFirstBatch = true; + + // 确定文件格式 + const fileFormat = exportOptions.fileFormat || 'json'; + const formatType = exportOptions.formatType || 'alpaca'; + + // 生成文件名 + const formatSuffixMap = { + alpaca: 'alpaca', + multilingualthinking: 'multilingual-thinking', + sharegpt: 'sharegpt', + custom: 'custom' + }; + const formatSuffix = formatSuffixMap[formatType] || formatType || 'export'; + const balanceSuffix = exportOptions.balanceMode ? '-balanced' : ''; + const dateStr = new Date().toISOString().slice(0, 10); + const fileName = `datasets-${projectId}-${formatSuffix}${balanceSuffix}-${dateStr}.${fileFormat}`; + + // 创建可写流 + let fileStream; + let writer; + + try { + // 使用 showSaveFilePicker API(现代浏览器) + if (window.showSaveFilePicker) { + const handle = await window.showSaveFilePicker({ + suggestedName: fileName, + types: [ + { + description: 'Dataset File', + accept: { + 'application/json': [`.${fileFormat}`] + } + } + ] + }); + fileStream = await handle.createWritable(); + } else { + // 降级方案:使用内存缓冲区(但分块处理) + fileStream = null; + } + } catch (err) { + // 用户取消或不支持,使用降级方案 + fileStream = null; + } + + // 如果不支持流式写入,使用分块累积方案 + let chunks = []; + let chunkCount = 0; + const MAX_CHUNKS_IN_MEMORY = 5; // 最多在内存中保留5批数据 + + // 写入文件头(JSON数组开始或CSV表头) + if (fileFormat === 'json') { + if (fileStream) { + await fileStream.write('[\n'); + } else { + chunks.push('[\n'); + } + } else if (fileFormat === 'csv') { + // 写入CSV表头 + const headers = getCSVHeaders(formatType, exportOptions); + const headerLine = headers.join(',') + '\n'; + if (fileStream) { + await fileStream.write(headerLine); + } else { + chunks.push(headerLine); + } + } + + // 分批获取和写入数据 + while (hasMore) { + const apiUrl = `/api/projects/${projectId}/datasets/export`; + const requestBody = { + batchMode: true, + offset: offset, + batchSize: batchSize + }; + + // 如果有选中的数据集 ID,传递 ID 列表 + if (exportOptions.selectedIds && exportOptions.selectedIds.length > 0) { + requestBody.selectedIds = exportOptions.selectedIds; + } else if (exportOptions.confirmedOnly) { + requestBody.status = 'confirmed'; + } + + // 检查是否是平衡导出模式 + if (exportOptions.balanceMode && exportOptions.balanceConfig) { + requestBody.balanceMode = true; + requestBody.balanceConfig = exportOptions.balanceConfig; + } + + const response = await axios.post(apiUrl, requestBody); + const batchResult = response.data; + + // 如果需要包含文本块内容,批量查询并填充 + if (exportOptions.customFields?.includeChunk && batchResult.data.length > 0) { + const chunkNames = batchResult.data.map(item => item.chunkName).filter(name => name); + + if (chunkNames.length > 0) { + try { + const chunkResponse = await axios.post(`/api/projects/${projectId}/chunks/batch-content`, { + chunkNames + }); + const chunkContentMap = chunkResponse.data; + + batchResult.data.forEach(item => { + if (item.chunkName && chunkContentMap[item.chunkName]) { + item.chunkContent = chunkContentMap[item.chunkName]; + } + }); + } catch (chunkError) { + console.error('获取文本块内容失败:', chunkError); + } + } + } + + // 转换当前批次数据 + const formattedBatch = formatDataBatch(batchResult.data, exportOptions); + + // 写入当前批次 + if (fileFormat === 'json') { + // 保持与原逻辑一致:JSON 导出为“格式化后的 JSON 数组”(2空格缩进) + // 每条记录单独 stringify + 缩进,并在数组级别拼接,避免一次性 stringify 全量数据导致内存暴涨 + const batchContent = formattedBatch + .map(item => { + const pretty = JSON.stringify(item, null, 2); + // 将对象的每一行整体再缩进 2 个空格,以符合数组元素缩进 + return ' ' + pretty.replace(/\n/g, '\n '); + }) + .join(',\n'); + + const content = isFirstBatch ? batchContent : ',\n' + batchContent; + + if (fileStream) { + await fileStream.write(content); + } else { + chunks.push(content); + chunkCount++; + } + } else if (fileFormat === 'jsonl') { + const batchContent = formattedBatch.map(item => JSON.stringify(item)).join('\n') + '\n'; + + if (fileStream) { + await fileStream.write(batchContent); + } else { + chunks.push(batchContent); + chunkCount++; + } + } else if (fileFormat === 'csv') { + const batchContent = formatBatchToCSV(formattedBatch, formatType, exportOptions); + + if (fileStream) { + await fileStream.write(batchContent); + } else { + chunks.push(batchContent); + chunkCount++; + } + } + + // 如果使用内存缓冲且累积了足够多的块,触发部分下载 + if (!fileStream && chunkCount >= MAX_CHUNKS_IN_MEMORY) { + // 这里我们仍然需要等到最后才能下载,但至少限制了内存使用 + // 可以考虑使用 Blob 分片 + } + + hasMore = batchResult.hasMore; + offset = batchResult.offset; + totalProcessed += batchResult.data.length; + isFirstBatch = false; + + // 通知进度更新 + if (onProgress) { + onProgress({ + processed: totalProcessed, + currentBatch: batchResult.data.length, + hasMore + }); + } + + // 避免过快请求 + if (hasMore) { + await new Promise(resolve => setTimeout(resolve, 50)); + } + } + + // 写入文件尾 + if (fileFormat === 'json') { + if (fileStream) { + await fileStream.write('\n]\n'); + await fileStream.close(); + } else { + chunks.push('\n]\n'); + } + } else { + if (fileStream) { + await fileStream.close(); + } + } + + // 如果使用内存缓冲方案,现在触发下载 + if (!fileStream) { + downloadFromChunks(chunks, fileName); + } + + toast.success(t('datasets.exportSuccess')); + return true; + } catch (error) { + console.error('Streaming export failed:', error); + toast.error(error.message || t('datasets.exportFailed')); + return false; + } + }; + + // 从内存块下载文件(优化版本,使用 Blob 流) + const downloadFromChunks = (chunks, fileName) => { + // 使用 Blob 构造函数,它会自动处理大数据 + const blob = new Blob(chunks, { type: 'application/octet-stream' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + // 延迟释放 URL,确保下载开始 + setTimeout(() => URL.revokeObjectURL(url), 1000); + }; + + // 获取CSV表头 + const getCSVHeaders = (formatType, exportOptions) => { + if (formatType === 'alpaca') { + return ['instruction', 'input', 'output', 'system']; + } else if (formatType === 'sharegpt') { + return ['messages']; + } else if (formatType === 'multilingualthinking') { + return ['reasoning_language', 'developer', 'user', 'analysis', 'final', 'messages']; + } else if (formatType === 'custom') { + const { questionField, answerField, cotField, includeLabels, includeChunk, questionOnly } = + exportOptions.customFields; + const headers = [questionField]; + if (!questionOnly) { + headers.push(answerField); + if (exportOptions.includeCOT && cotField) { + headers.push(cotField); + } + } + if (includeLabels) headers.push('label'); + if (includeChunk) headers.push('chunk'); + return headers; + } + return []; + }; + + // 格式化数据批次 + const formatDataBatch = (dataBatch, exportOptions) => { + const formatType = exportOptions.formatType || 'alpaca'; + + if (formatType === 'alpaca') { + if (exportOptions.alpacaFieldType === 'instruction') { + return dataBatch.map(({ question, answer, cot }) => ({ + instruction: question, + input: '', + output: cot && exportOptions.includeCOT ? `${cot}\n${answer}` : answer, + system: exportOptions.systemPrompt || '' + })); + } else { + return dataBatch.map(({ question, answer, cot }) => ({ + instruction: exportOptions.customInstruction || '', + input: question, + output: cot && exportOptions.includeCOT ? `${cot}\n${answer}` : answer, + system: exportOptions.systemPrompt || '' + })); + } + } else if (formatType === 'sharegpt') { + return dataBatch.map(({ question, answer, cot }) => { + const messages = []; + if (exportOptions.systemPrompt) { + messages.push({ role: 'system', content: exportOptions.systemPrompt }); + } + messages.push({ + role: 'user', + content: question + }); + messages.push({ + role: 'assistant', + content: cot && exportOptions.includeCOT ? `${cot}\n${answer}` : answer + }); + return { messages }; + }); + } else if (formatType === 'multilingualthinking') { + return dataBatch.map(({ question, answer, cot }) => ({ + reasoning_language: exportOptions.reasoningLanguage || 'English', + developer: exportOptions.systemPrompt || '', + user: question, + analysis: exportOptions.includeCOT && cot ? cot : null, + final: answer, + messages: [ + { + content: exportOptions.systemPrompt || '', + role: 'system', + thinking: null + }, + { + content: question, + role: 'user', + thinking: null + }, + { + content: answer, + role: 'assistant', + thinking: exportOptions.includeCOT && cot ? cot : null + } + ] + })); + } else if (formatType === 'custom') { + const { questionField, answerField, cotField, includeLabels, includeChunk, questionOnly } = + exportOptions.customFields; + return dataBatch.map(({ question, answer, cot, questionLabel: labels, chunkContent }) => { + const item = { [questionField]: question }; + if (!questionOnly) { + item[answerField] = answer; + if (cot && exportOptions.includeCOT && cotField) { + item[cotField] = cot; + } + } + if (includeLabels && labels && labels.length > 0) { + item.label = labels.split(' ')[1]; + } + if (includeChunk && chunkContent) { + item.chunk = chunkContent; + } + return item; + }); + } + return dataBatch; + }; + + // 将批次格式化为CSV行 + const formatBatchToCSV = (formattedBatch, formatType, exportOptions) => { + const headers = getCSVHeaders(formatType, exportOptions); + return ( + formattedBatch + .map(item => { + return headers + .map(header => { + let field = item[header]?.toString() || ''; + // 对于复杂对象,转换为JSON字符串 + if (typeof item[header] === 'object') { + field = JSON.stringify(item[header]); + } + // CSV转义 + if (field.includes(',') || field.includes('\n') || field.includes('"')) { + field = `"${field.replace(/"/g, '""')}"`; + } + return field; + }) + .join(','); + }) + .join('\n') + '\n' + ); + }; + + // 处理和下载数据的通用函数(保留用于小数据量) + const processAndDownloadData = async (dataToExport, exportOptions) => { + const formattedData = formatDataBatch(dataToExport, exportOptions); + + let content; + let fileExtension; + const fileFormat = exportOptions.fileFormat || 'json'; + + if (fileFormat === 'jsonl') { + content = formattedData.map(item => JSON.stringify(item)).join('\n'); + fileExtension = 'jsonl'; + } else if (fileFormat === 'csv') { + const headers = getCSVHeaders(exportOptions.formatType, exportOptions); + const csvRows = [ + headers.join(','), + ...formattedData.map(item => + headers + .map(header => { + let field = item[header]?.toString() || ''; + if (typeof item[header] === 'object') { + field = JSON.stringify(item[header]); + } + if (field.includes(',') || field.includes('\n') || field.includes('"')) { + field = `"${field.replace(/"/g, '""')}"`; + } + return field; + }) + .join(',') + ) + ]; + content = csvRows.join('\n'); + fileExtension = 'csv'; + } else { + content = JSON.stringify(formattedData, null, 2); + fileExtension = 'json'; + } + + const blob = new Blob([content], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + + const formatSuffixMap = { + alpaca: 'alpaca', + multilingualthinking: 'multilingual-thinking', + sharegpt: 'sharegpt', + custom: 'custom' + }; + const formatSuffix = formatSuffixMap[exportOptions.formatType] || exportOptions.formatType || 'export'; + const balanceSuffix = exportOptions.balanceMode ? '-balanced' : ''; + const dateStr = new Date().toISOString().slice(0, 10); + a.download = `datasets-${projectId}-${formatSuffix}${balanceSuffix}-${dateStr}.${fileExtension}`; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + // 导出数据集(保持向后兼容的原有功能) + const exportDatasets = async exportOptions => { + try { + const apiUrl = `/api/projects/${projectId}/datasets/export`; + const requestBody = {}; + + if (exportOptions.selectedIds && exportOptions.selectedIds.length > 0) { + requestBody.selectedIds = exportOptions.selectedIds; + } else if (exportOptions.confirmedOnly) { + requestBody.status = 'confirmed'; + } + + if (exportOptions.balanceMode && exportOptions.balanceConfig) { + requestBody.balanceMode = true; + requestBody.balanceConfig = exportOptions.balanceConfig; + } + + const response = await axios.post(apiUrl, requestBody); + let dataToExport = response.data; + + await processAndDownloadData(dataToExport, exportOptions); + + toast.success(t('datasets.exportSuccess')); + return true; + } catch (error) { + toast.error(error.message); + return false; + } + }; + + // 导出平衡数据集 + const exportBalancedDataset = async exportOptions => { + const balancedOptions = { + ...exportOptions, + balanceMode: true, + balanceConfig: exportOptions.balanceConfig + }; + return await exportDatasets(balancedOptions); + }; + + return { + exportDatasets, + exportBalancedDataset, + exportDatasetsStreaming + }; +}; + +export default useDatasetExport; +export { useDatasetExport }; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetFilters.js b/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetFilters.js new file mode 100644 index 0000000..e114fb7 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/hooks/useDatasetFilters.js @@ -0,0 +1,171 @@ +'use client'; + +import { useState, useEffect } from 'react'; + +/** + * 数据集筛选条件持久化 Hook + * 负责筛选条件的保存、恢复和管理 + * @param {string} projectId - 项目ID + * @returns {Object} 筛选条件和相关方法 + */ +export function useDatasetFilters(projectId) { + const [filterConfirmed, setFilterConfirmed] = useState('all'); + const [filterHasCot, setFilterHasCot] = useState('all'); + const [filterIsDistill, setFilterIsDistill] = useState('all'); + const [filterScoreRange, setFilterScoreRange] = useState([0, 5]); + const [filterCustomTag, setFilterCustomTag] = useState(''); + const [filterNoteKeyword, setFilterNoteKeyword] = useState(''); + const [filterChunkName, setFilterChunkName] = useState(''); + const [searchQuery, setSearchQuery] = useState(''); + const [searchField, setSearchField] = useState('question'); + const [page, setPage] = useState(1); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [isInitialized, setIsInitialized] = useState(false); + + // 从 localStorage 恢复筛选条件 + useEffect(() => { + if (typeof window !== 'undefined') { + try { + const savedFilters = localStorage.getItem(`datasets-filters-${projectId}`); + if (savedFilters) { + const filters = JSON.parse(savedFilters); + setFilterConfirmed(filters.filterConfirmed || 'all'); + setFilterHasCot(filters.filterHasCot || 'all'); + setFilterIsDistill(filters.filterIsDistill || 'all'); + setFilterScoreRange(filters.filterScoreRange || [0, 5]); + setFilterCustomTag(filters.filterCustomTag || ''); + setFilterNoteKeyword(filters.filterNoteKeyword || ''); + setFilterChunkName(filters.filterChunkName || ''); + setSearchQuery(filters.searchQuery || ''); + setSearchField(filters.searchField || 'question'); + setPage(filters.page || 1); + setRowsPerPage(filters.rowsPerPage || 10); + } + } catch (error) { + console.error('恢复筛选条件失败:', error); + } + setIsInitialized(true); + } + }, [projectId]); + + // 保存筛选条件到 localStorage + useEffect(() => { + if (typeof window !== 'undefined' && isInitialized) { + try { + const filters = { + filterConfirmed, + filterHasCot, + filterIsDistill, + filterScoreRange, + filterCustomTag, + filterNoteKeyword, + filterChunkName, + searchQuery, + searchField, + page, + rowsPerPage + }; + localStorage.setItem(`datasets-filters-${projectId}`, JSON.stringify(filters)); + } catch (error) { + console.error('保存筛选条件失败:', error); + } + } + }, [ + projectId, + filterConfirmed, + filterHasCot, + filterIsDistill, + filterScoreRange, + filterCustomTag, + filterNoteKeyword, + filterChunkName, + searchQuery, + searchField, + page, + rowsPerPage, + isInitialized + ]); + + /** + * 重置所有筛选条件为默认值 + */ + const resetFilters = () => { + setFilterConfirmed('all'); + setFilterHasCot('all'); + setFilterIsDistill('all'); + setFilterScoreRange([0, 5]); + setFilterCustomTag(''); + setFilterNoteKeyword(''); + setFilterChunkName(''); + setSearchQuery(''); + setSearchField('question'); + setPage(1); + setRowsPerPage(10); + }; + + /** + * 清除 localStorage 中的筛选条件 + */ + const clearSavedFilters = () => { + if (typeof window !== 'undefined') { + try { + localStorage.removeItem(`datasets-filters-${projectId}`); + } catch (error) { + console.error('清除筛选条件失败:', error); + } + } + }; + + /** + * 计算当前活跃的筛选条件数量 + * @returns {number} 活跃筛选条件的数量 + */ + const getActiveFilterCount = () => { + let count = 0; + + if (filterConfirmed !== 'all') count++; + if (filterHasCot !== 'all') count++; + if (filterIsDistill !== 'all') count++; + if (filterScoreRange[0] > 0 || filterScoreRange[1] < 5) count++; + if (filterCustomTag) count++; + if (filterNoteKeyword) count++; + if (filterChunkName) count++; + + return count; + }; + + return { + // 筛选条件状态 + filterConfirmed, + setFilterConfirmed, + filterHasCot, + setFilterHasCot, + filterIsDistill, + setFilterIsDistill, + filterScoreRange, + setFilterScoreRange, + filterCustomTag, + setFilterCustomTag, + filterNoteKeyword, + setFilterNoteKeyword, + filterChunkName, + setFilterChunkName, + searchQuery, + setSearchQuery, + searchField, + setSearchField, + // 分页状态 + page, + setPage, + rowsPerPage, + setRowsPerPage, + // 初始化状态 + isInitialized, + // 工具方法 + resetFilters, + clearSavedFilters, + getActiveFilterCount + }; +} + +export default useDatasetFilters; diff --git a/easy-dataset-main/app/projects/[projectId]/datasets/page.js b/easy-dataset-main/app/projects/[projectId]/datasets/page.js new file mode 100644 index 0000000..6504f50 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/datasets/page.js @@ -0,0 +1,596 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { Container, Box, Typography, Button, Card, useTheme, alpha } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { useRouter } from 'next/navigation'; +import ExportDatasetDialog from '@/components/ExportDatasetDialog'; +import ExportProgressDialog from '@/components/ExportProgressDialog'; +import ImportDatasetDialog from '@/components/datasets/ImportDatasetDialog'; +import { useTranslation } from 'react-i18next'; +import DatasetList from './components/DatasetList'; +import SearchBar from './components/SearchBar'; +import ActionBar from './components/ActionBar'; +import FilterDialog from './components/FilterDialog'; +import DeleteConfirmDialog from './components/DeleteConfirmDialog'; +import useDatasetExport from './hooks/useDatasetExport'; +import useDatasetEvaluation from './hooks/useDatasetEvaluation'; +import useDatasetFilters from './hooks/useDatasetFilters'; +import { processInParallel } from '@/lib/util/async'; +import axios from 'axios'; +import { useDebounce } from '@/hooks/useDebounce'; +import { toast } from 'sonner'; + +// 主页面组件 +export default function DatasetsPage({ params }) { + const { projectId } = params; + const router = useRouter(); + const theme = useTheme(); + const [datasets, setDatasets] = useState({ data: [], total: 0, confirmedCount: 0 }); + const [loading, setLoading] = useState(true); + const [deleteDialog, setDeleteDialog] = useState({ + open: false, + datasets: null, + batch: false, + deleting: false + }); + const [exportDialog, setExportDialog] = useState({ open: false }); + const [importDialog, setImportDialog] = useState({ open: false }); + const [selectedIds, setselectedIds] = useState([]); + const [availableTags, setAvailableTags] = useState([]); + const [filterDialogOpen, setFilterDialogOpen] = useState(false); + const { t } = useTranslation(); + + // 使用 useDatasetFilters Hook 管理筛选条件 + const { + filterConfirmed, + setFilterConfirmed, + filterHasCot, + setFilterHasCot, + filterIsDistill, + setFilterIsDistill, + filterScoreRange, + setFilterScoreRange, + filterCustomTag, + setFilterCustomTag, + filterNoteKeyword, + setFilterNoteKeyword, + filterChunkName, + setFilterChunkName, + searchQuery, + setSearchQuery, + searchField, + setSearchField, + page, + setPage, + rowsPerPage, + setRowsPerPage, + isInitialized, + getActiveFilterCount + } = useDatasetFilters(projectId); + + const debouncedSearchQuery = useDebounce(searchQuery); + // 删除进度状态 + const [deleteProgress, setDeteleProgress] = useState({ + total: 0, // 总删除问题数量 + completed: 0, // 已删除完成的数量 + percentage: 0 // 进度百分比 + }); + // 导出进度状态 + const [exportProgress, setExportProgress] = useState({ + show: false, // 是否显示进度 + processed: 0, // 已处理数量 + total: 0, // 总数量 + hasMore: true // 是否还有更多数据 + }); + + // 3. 添加打开导出对话框的处理函数 + const handleOpenExportDialog = () => { + setExportDialog({ open: true }); + }; + + // 4. 添加关闭导出对话框的处理函数 + const handleCloseExportDialog = () => { + setExportDialog({ open: false }); + }; + + // 5. 添加打开导入对话框的处理函数 + const handleOpenImportDialog = () => { + setImportDialog({ open: true }); + }; + + // 6. 添加关闭导入对话框的处理函数 + const handleCloseImportDialog = () => { + setImportDialog({ open: false }); + }; + + // 7. 导入成功后的处理函数 + const handleImportSuccess = () => { + // 刷新数据集列表 + getDatasetsList(); + toast.success(t('import.importSuccess', '数据集导入成功')); + }; + + // 获取数据集列表 + const getDatasetsList = useCallback( + async ({ pageOverride } = {}) => { + const effectivePage = pageOverride ?? page; + try { + setLoading(true); + let url = `/api/projects/${projectId}/datasets?page=${effectivePage}&size=${rowsPerPage}`; + + if (filterConfirmed !== 'all') { + url += `&status=${filterConfirmed}`; + } + + if (debouncedSearchQuery) { + url += `&input=${encodeURIComponent(debouncedSearchQuery)}&field=${searchField}`; + } + + if (filterHasCot !== 'all') { + url += `&hasCot=${filterHasCot}`; + } + + if (filterIsDistill !== 'all') { + url += `&isDistill=${filterIsDistill}`; + } + + if (filterScoreRange[0] > 0 || filterScoreRange[1] < 5) { + url += `&scoreRange=${filterScoreRange[0]}-${filterScoreRange[1]}`; + } + + if (filterCustomTag) { + url += `&customTag=${encodeURIComponent(filterCustomTag)}`; + } + + if (filterNoteKeyword) { + url += `¬eKeyword=${encodeURIComponent(filterNoteKeyword)}`; + } + + if (filterChunkName) { + url += `&chunkName=${encodeURIComponent(filterChunkName)}`; + } + + const response = await axios.get(url); + setDatasets(response.data || { data: [], total: 0, confirmedCount: 0 }); + } catch (error) { + toast.error(error.message); + } finally { + setLoading(false); + } + }, + [ + debouncedSearchQuery, + filterConfirmed, + filterCustomTag, + filterHasCot, + filterIsDistill, + filterNoteKeyword, + filterChunkName, + filterScoreRange, + page, + projectId, + rowsPerPage, + searchField + ] + ); + + useEffect(() => { + if (!isInitialized) return; + + getDatasetsList(); + // 获取项目中所有使用过的标签 + const fetchAvailableTags = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/datasets/tags`); + if (response.ok) { + const data = await response.json(); + setAvailableTags(data.tags || []); + } + } catch (error) { + console.error('获取标签失败:', error); + } + }; + fetchAvailableTags(); + }, [projectId, page, rowsPerPage, debouncedSearchQuery, searchField, isInitialized]); + + // 处理页码变化 + const handlePageChange = (_event, newPage) => { + // MUI TablePagination 的页码从 0 开始,而我们的 API 从 1 开始 + setPage(newPage + 1); + }; + + // 处理每页行数变化 + const handleRowsPerPageChange = event => { + setPage(1); + setRowsPerPage(parseInt(event.target.value, 10)); + }; + + // 打开删除确认框 + const handleOpenDeleteDialog = dataset => { + setDeleteDialog({ + open: true, + datasets: [dataset] + }); + }; + + // 关闭删除确认框 + const handleCloseDeleteDialog = () => { + setDeleteDialog({ + open: false, + dataset: null + }); + }; + + const handleBatchDeleteDataset = async () => { + const datasetsArray = selectedIds.map(id => ({ id })); + setDeleteDialog({ + open: true, + datasets: datasetsArray, + batch: true, + count: selectedIds.length + }); + }; + + const resetProgress = () => { + setDeteleProgress({ + total: deleteDialog.count, + completed: 0, + percentage: 0 + }); + }; + + const handleDeleteConfirm = async () => { + if (deleteDialog.batch) { + setDeleteDialog({ + ...deleteDialog, + deleting: true + }); + await handleBatchDelete(); + resetProgress(); + } else { + const [dataset] = deleteDialog.datasets; + if (!dataset) return; + await handleDelete(dataset); + } + setselectedIds([]); + // 刷新数据 + getDatasetsList(); + // 关闭确认框 + handleCloseDeleteDialog(); + }; + + // 批量删除数据集 + const handleBatchDelete = async () => { + try { + await processInParallel( + selectedIds, + async datasetId => { + await fetch(`/api/projects/${projectId}/datasets?id=${datasetId}`, { + method: 'DELETE' + }); + }, + 3, + (cur, total) => { + setDeteleProgress({ + total, + completed: cur, + percentage: Math.floor((cur / total) * 100) + }); + } + ); + + toast.success(t('common.deleteSuccess')); + } catch (error) { + console.error('批量删除失败:', error); + toast.error(error.message || t('common.deleteFailed')); + } + }; + + // 删除数据集 + const handleDelete = async dataset => { + try { + const response = await fetch(`/api/projects/${projectId}/datasets?id=${dataset.id}`, { + method: 'DELETE' + }); + if (!response.ok) throw new Error(t('datasets.deleteFailed')); + + toast.success(t('datasets.deleteSuccess')); + } catch (error) { + toast.error(error.message || t('datasets.deleteFailed')); + } + }; + + // 使用自定义 Hook 处理数据集导出逻辑 + const { exportDatasets, exportDatasetsStreaming } = useDatasetExport(projectId); + + // 使用自定义 Hook 处理数据集评估逻辑 + const { evaluatingIds, batchEvaluating, handleEvaluateDataset, handleBatchEvaluate } = useDatasetEvaluation( + projectId, + getDatasetsList + ); + + // 处理导出数据集 - 智能选择导出方式 + const handleExportDatasets = async exportOptions => { + try { + // 如果是平衡导出,则忽略选中项,按 balanceConfig 导出 + const exportOptionsWithSelection = exportOptions.balanceMode + ? { ...exportOptions } + : { ...exportOptions, ...(selectedIds.length > 0 && { selectedIds }) }; + + // 获取数据总量: + // 平衡导出时,按 balanceConfig 的总量计算; + // 其他情况:如果有选中数据集则使用选中数量,否则使用当前筛选条件下的数据总量 + const balancedTotal = Array.isArray(exportOptions.balanceConfig) + ? exportOptions.balanceConfig.reduce((sum, c) => sum + (parseInt(c.maxCount) || 0), 0) + : 0; + const totalCount = exportOptions.balanceMode + ? balancedTotal + : selectedIds.length > 0 + ? selectedIds.length + : datasets.total || 0; + + // 设置阈值:超过1000条数据使用流式导出 + const STREAMING_THRESHOLD = 1000; + + // 检查是否需要包含文本块内容 + const needsChunkContent = exportOptions.formatType === 'custom' && exportOptions.customFields?.includeChunk; + + let success = false; + + // 如果数据量大于阈值或需要查询文本块内容,使用流式导出 + if (totalCount > STREAMING_THRESHOLD || needsChunkContent) { + // 使用流式导出,显示进度 + setExportProgress({ show: true, processed: 0, total: totalCount }); + + success = await exportDatasetsStreaming(exportOptionsWithSelection, progress => { + setExportProgress(prev => ({ + ...prev, + processed: progress.processed, + hasMore: progress.hasMore + })); + }); + + // 隐藏进度 + setExportProgress({ show: false, processed: 0, total: 0 }); + } else { + // 使用传统导出方式 + success = await exportDatasets(exportOptionsWithSelection); + } + + if (success) { + // 关闭export对话框 + handleCloseExportDialog(); + } + } catch (error) { + console.error('Export failed:', error); + setExportProgress({ show: false, processed: 0, total: 0 }); + } + }; + + // 查看详情 + const handleViewDetails = id => { + router.push(`/projects/${projectId}/datasets/${id}`); + }; + + // 处理全选/取消全选 + const handleSelectAll = async event => { + if (event.target.checked) { + // 获取所有符合当前筛选条件的数据,不受分页限制 + let url = `/api/projects/${projectId}/datasets?selectedAll=1`; + + if (filterConfirmed !== 'all') { + url += `&status=${filterConfirmed}`; + } + + if (debouncedSearchQuery) { + url += `&input=${encodeURIComponent(debouncedSearchQuery)}&field=${searchField}`; + } + + if (filterHasCot !== 'all') { + url += `&hasCot=${filterHasCot}`; + } + + if (filterIsDistill !== 'all') { + url += `&isDistill=${filterIsDistill}`; + } + + if (filterScoreRange[0] > 0 || filterScoreRange[1] < 5) { + url += `&scoreRange=${filterScoreRange[0]}-${filterScoreRange[1]}`; + } + + if (filterCustomTag) { + url += `&customTag=${encodeURIComponent(filterCustomTag)}`; + } + + if (filterNoteKeyword) { + url += `¬eKeyword=${encodeURIComponent(filterNoteKeyword)}`; + } + + const response = await axios.get(url); + setselectedIds(response.data.map(dataset => dataset.id)); + } else { + setselectedIds([]); + } + }; + + // 处理单个选择 + const handleSelectItem = id => { + setselectedIds(prev => { + if (prev.includes(id)) { + return prev.filter(item => item !== id); + } else { + return [...prev, id]; + } + }); + }; + + const handleResetFilters = useCallback(() => { + setFilterConfirmed('all'); + setFilterHasCot('all'); + setFilterIsDistill('all'); + setFilterScoreRange([0, 5]); + setFilterCustomTag(''); + setFilterNoteKeyword(''); + setFilterChunkName(''); + setPage(1); + getDatasetsList({ pageOverride: 1 }); + }, [ + getDatasetsList, + setFilterConfirmed, + setFilterHasCot, + setFilterIsDistill, + setFilterScoreRange, + setFilterCustomTag, + setFilterNoteKeyword, + setFilterChunkName, + setPage + ]); + + const handleApplyFilters = useCallback(() => { + setFilterDialogOpen(false); + setPage(1); + getDatasetsList({ pageOverride: 1 }); + }, [getDatasetsList, setFilterDialogOpen, setPage]); + const handleCloseFilterDialog = useCallback(() => setFilterDialogOpen(false), [setFilterDialogOpen]); + + return ( + + + + { + setSearchQuery(value); + setPage(1); + }} + onSearchFieldChange={value => { + setSearchField(value); + setPage(1); + }} + onMoreFiltersClick={() => setFilterDialogOpen(true)} + activeFilterCount={getActiveFilterCount()} + /> + + + + {selectedIds.length ? ( + + + {t('datasets.selected', { + count: selectedIds.length + })} + + + + ) : ( + '' + )} + + + + + + + + + + + + {/* 导出进度对话框 */} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/distill/autoDistillService.js b/easy-dataset-main/app/projects/[projectId]/distill/autoDistillService.js new file mode 100644 index 0000000..bb7cdc7 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/distill/autoDistillService.js @@ -0,0 +1,870 @@ +'use client'; + +import axios from 'axios'; + +/** + * 自动蒸馏服务 + */ +class AutoDistillService { + /** + * 执行自动蒸馆任务 + * @param {Object} config - 配置信息 + * @param {string} config.projectId - 项目ID + * @param {string} config.topic - 蒸馆主题 + * @param {number} config.levels - 标签层级 + * @param {number} config.tagsPerLevel - 每层标签数量 + * @param {number} config.questionsPerTag - 每个标签问题数量 + * @param {Object} config.model - 模型信息 + * @param {string} config.language - 语言 + * @param {Function} config.onProgress - 进度回调 + * @param {Function} config.onLog - 日志回调 + * @returns {Promise} + */ + async executeDistillTask(config) { + const { + projectId, + topic, + levels, + tagsPerLevel, + questionsPerTag, + model, + language, + datasetType = 'single-turn', // 新增数据集类型 + concurrencyLimit = 5, + onProgress, + onLog + } = config; + + // 项目名称存储,用于整个流程共享 + this.projectName = ''; + + try { + // 初始化进度信息 + if (onProgress) { + onProgress({ + stage: 'initializing', + tagsTotal: 0, + tagsBuilt: 0, + questionsTotal: 0, + questionsBuilt: 0, + datasetsTotal: 0, + datasetsBuilt: 0 + }); + } + + // 获取项目名称,只需获取一次 + try { + const projectResponse = await axios.get(`/api/projects/${projectId}`); + if (projectResponse && projectResponse.data && projectResponse.data.name) { + this.projectName = projectResponse.data.name; + this.addLog(onLog, `Using project name "${this.projectName}" as the top-level tag`); + } else { + this.projectName = topic; // 如果无法获取项目名称,则使用主题作为默认值 + this.addLog(onLog, `Could not find project name, using topic "${topic}" as the top-level tag`); + } + } catch (error) { + this.projectName = topic; // 出错时使用主题作为默认值 + this.addLog(onLog, `Failed to get project name, using topic "${topic}" instead: ${error.message}`); + } + + // 添加日志 + this.addLog( + onLog, + `Starting to build tag tree for "${topic}", number of levels: ${levels}, tags per level: ${tagsPerLevel}, questions per tag: ${questionsPerTag}` + ); + + // 从根节点开始构建标签树 + await this.buildTagTree({ + projectId, + topic, + levels, + tagsPerLevel, + model, + language, + onProgress, + onLog + }); + + // 所有标签构建完成后,生成问题 + await this.generateQuestionsForTags({ + projectId, + levels, + questionsPerTag, + model, + language, + concurrencyLimit, + onProgress, + onLog + }); + + // 根据数据集类型生成不同类型的数据集 + if (datasetType === 'single-turn') { + // 只生成单轮对话数据集 + await this.generateDatasetsForQuestions({ + projectId, + model, + language, + concurrencyLimit, + onProgress, + onLog + }); + } else if (datasetType === 'multi-turn') { + // 只生成多轮对话数据集 + await this.generateMultiTurnDatasetsForQuestions({ + projectId, + model, + language, + concurrencyLimit, + onProgress, + onLog + }); + } else if (datasetType === 'both') { + // 先生成单轮对话数据集 + await this.generateDatasetsForQuestions({ + projectId, + model, + language, + concurrencyLimit, + onProgress, + onLog + }); + // 再生成多轮对话数据集 + await this.generateMultiTurnDatasetsForQuestions({ + projectId, + model, + language, + concurrencyLimit, + onProgress, + onLog + }); + } + + // 任务完成 + if (onProgress) { + onProgress({ + stage: 'completed' + }); + } + + this.addLog(onLog, 'Auto distillation task completed'); + } catch (error) { + console.error('自动蒸馏任务执行失败:', error); + this.addLog(onLog, `Task execution error: ${error.message || 'Unknown error'}`); + throw error; + } + } + + /** + * 构建标签树 + * @param {Object} config - 配置信息 + * @param {string} config.projectId - 项目ID + * @param {string} config.topic - 蒸馆主题 + * @param {number} config.levels - 标签层级 + * @param {number} config.tagsPerLevel - 每层标签数量 + * @param {Object} config.model - 模型信息 + * @param {string} config.language - 语言 + * @param {Function} config.onProgress - 进度回调 + * @param {Function} config.onLog - 日志回调 + * @returns {Promise} + */ + async buildTagTree(config) { + const { projectId, topic, levels, tagsPerLevel, model, language, onProgress, onLog } = config; + + // 使用已经获取的项目名称,如果未获取到,则使用主题 + const projectName = this.projectName || topic; + + try { + // 设置初始阶段 + if (onProgress) { + onProgress({ + stage: 'level1' + }); + } + + // 获取所有现有标签 + let allTags = []; + try { + const response = await axios.get(`/api/projects/${projectId}/distill/tags/all`); + allTags = response.data; + } catch (error) { + console.error('获取标签失败:', error); + this.addLog(onLog, `Failed to get tags: ${error.message}`); + return; + } + + // 获取叶子节点总数,更新进度条 + const leafTags = Math.pow(tagsPerLevel, levels); + if (onProgress) { + onProgress({ + tagsTotal: leafTags + }); + } + + // 批量构建标签树 + await this.batchBuildTagTree({ + projectId, + topic, + levels, + tagsPerLevel, + model, + language, + projectName, + allTags, + onProgress, + onLog + }); + } catch (error) { + console.error('构建标签树失败:', error); + this.addLog(onLog, `Failed to build tag tree: ${error.message}`); + throw error; + } + } + + /** + * 批量构建标签树 + * @param {Object} config - 配置信息 + * @returns {Promise} + */ + async batchBuildTagTree(config) { + const { + projectId, + topic, + levels, + tagsPerLevel, + model, + language, + projectName, + allTags: initialTags, + onProgress, + onLog + } = config; + + // 创建一个本地标签缓存,避免频繁请求服务器 + let allTags = [...initialTags]; + + // 构建父子关系映射 + const childrenMap = {}; + const parentMap = {}; + allTags.forEach(tag => { + parentMap[tag.id] = tag; + if (tag.parentId) { + if (!childrenMap[tag.parentId]) { + childrenMap[tag.parentId] = []; + } + childrenMap[tag.parentId].push(tag); + } + }); + + // 按层级分组标签,提高查找效率 + const tagsByLevel = {}; + allTags.forEach(tag => { + const depth = this.getTagDepth(tag, parentMap); + if (!tagsByLevel[depth]) { + tagsByLevel[depth] = []; + } + tagsByLevel[depth].push(tag); + }); + + // 批量创建各层级标签 + for (let level = 1; level <= levels; level++) { + // 设置当前阶段 + if (onProgress) { + onProgress({ + stage: `level${level}` + }); + } + + // 确定当前层级的父标签 + let parentTags = []; + if (level === 1) { + // 第一层标签没有父标签 + parentTags = [null]; + } else { + // 获取上一层的标签作为父标签 + parentTags = tagsByLevel[level - 1] || []; + } + + const batch = parentTags; + const creationPromises = []; + + for (const parentTag of batch) { + // 获取当前父标签下的子标签 + let currentLevelTags = []; + if (parentTag) { + currentLevelTags = childrenMap[parentTag.id] || []; + } else { + // 根标签(没有父标签的标签) + currentLevelTags = allTags.filter(tag => !tag.parentId); + } + + // 计算需要创建的标签数量 + const needToCreate = Math.max(0, tagsPerLevel - currentLevelTags.length); + + if (needToCreate > 0) { + // 构建标签路径 + let tagPathWithProjectName; + if (level === 1) { + // 第一层使用项目名称 + tagPathWithProjectName = projectName; + } else { + // 其他层构建完整路径 + const parentTagName = parentTag?.label || ''; + const parentTagPath = this.getTagPath(parentTag, parentMap); + + if (!parentTagPath) { + tagPathWithProjectName = projectName; + } else if (!parentTagPath.startsWith(projectName)) { + tagPathWithProjectName = `${projectName} > ${parentTagPath}`; + } else { + tagPathWithProjectName = parentTagPath; + } + } + + // 创建标签的Promise + const createPromise = axios + .post(`/api/projects/${projectId}/distill/tags`, { + parentTag: level === 1 ? topic : parentTag?.label || '', + parentTagId: parentTag ? parentTag.id : null, + tagPath: tagPathWithProjectName || (level === 1 ? projectName : ''), + count: needToCreate, + model, + language + }) + .then(response => { + // 更新本地标签缓存 + const newTags = response.data; + allTags = [...allTags, ...newTags]; + + // 更新父子关系映射 + if (parentTag) { + if (!childrenMap[parentTag.id]) { + childrenMap[parentTag.id] = []; + } + childrenMap[parentTag.id].push(...newTags); + } + + // 更新父标签映射 + newTags.forEach(tag => { + parentMap[tag.id] = tag; + }); + + // 更新层级分组 + if (!tagsByLevel[level]) { + tagsByLevel[level] = []; + } + tagsByLevel[level].push(...newTags); + + // 更新构建的标签数量 + if (onProgress) { + onProgress({ + tagsBuilt: newTags.length, + updateType: 'increment' + }); + } + + // 添加日志 + this.addLog( + onLog, + `Successfully created ${newTags.length} tags: ${newTags.map(tag => `"${tag.label}"`).join(', ')}` + ); + + return newTags; + }) + .catch(error => { + console.error(`创建${level}级标签失败:`, error); + this.addLog(onLog, `Failed to create ${level} level tags: ${error.message || 'Unknown error'}`); + return []; + }); + + creationPromises.push(createPromise); + } + } + + // 并行执行当前批次的所有创建任务 + await Promise.all(creationPromises); + } + } + + /** + * 为标签生成问题 + * @param {Object} config - 配置信息 + * @param {string} config.projectId - 项目ID + * @param {number} config.levels - 标签层级 + * @param {number} config.questionsPerTag - 每个标签问题数量 + * @param {Object} config.model - 模型信息 + * @param {string} config.language - 语言 + * @param {Function} config.onProgress - 进度回调 + * @param {Function} config.onLog - 日志回调 + * @returns {Promise} + */ + async generateQuestionsForTags(config) { + const { projectId, levels, questionsPerTag, model, language, concurrencyLimit = 5, onProgress, onLog } = config; + + // 设置当前阶段 + if (onProgress) { + onProgress({ + stage: 'questions' + }); + } + + this.addLog(onLog, 'Tag tree built, starting to generate questions for leaf tags...'); + + try { + // 获取所有标签 + const response = await axios.get(`/api/projects/${projectId}/distill/tags/all`); + const allTags = response.data; + + // 找出所有叶子标签(没有子标签的标签) + const leafTags = []; + + // 创建一个映射表,记录每个标签的子标签 + const childrenMap = {}; + const parentMap = {}; + allTags.forEach(tag => { + parentMap[tag.id] = tag; + if (tag.parentId) { + if (!childrenMap[tag.parentId]) { + childrenMap[tag.parentId] = []; + } + childrenMap[tag.parentId].push(tag); + } + }); + + // 找出所有叶子标签 + allTags.forEach(tag => { + // 如果没有子标签,并且深度是最大层级,则为叶子标签 + if (!childrenMap[tag.id] && this.getTagDepth(tag, parentMap) === levels) { + leafTags.push(tag); + } + }); + + this.addLog(onLog, `Found ${leafTags.length} leaf tags, starting to generate questions...`); + + // 获取所有问题 + const questionsResponse = await axios.get(`/api/projects/${projectId}/questions/tree?isDistill=true`); + const allQuestions = questionsResponse.data; + + // 更新总问题数量 + const totalQuestionsToGenerate = leafTags.length * questionsPerTag; + if (onProgress) { + onProgress({ + questionsTotal: totalQuestionsToGenerate + }); + } + + // 准备并发任务 + const generateQuestionTasks = []; + const processedTags = []; + + // 准备所有需要生成问题的叶子标签任务 + for (const tag of leafTags) { + // 获取标签路径 + const tagPath = this.getTagPath(tag, parentMap); + + // 计算已有问题数量 + const existingQuestions = allQuestions.filter(q => q.label === tag.label); + const needToCreate = Math.max(0, questionsPerTag - existingQuestions.length); + + if (needToCreate > 0) { + // 只添加需要生成问题的标签任务 + generateQuestionTasks.push({ + tag, + tagPath, + needToCreate + }); + + this.addLog(onLog, `Preparing to generate ${needToCreate} questions for tag "${tag.label}"...`); + } else { + this.addLog( + onLog, + `Tag "${tag.label}" already has ${existingQuestions.length} questions, no need to generate new questions` + ); + } + } + + // 分批执行生成问题任务,控制并发数 + this.addLog( + onLog, + `Total ${generateQuestionTasks.length} tags need questions, concurrency limit: ${concurrencyLimit}` + ); + + // 使用分组批量处理 + for (let i = 0; i < generateQuestionTasks.length; i += concurrencyLimit) { + const batch = generateQuestionTasks.slice(i, i + concurrencyLimit); + + // 并行处理批次任务 + await Promise.all( + batch.map(async task => { + const { tag, tagPath, needToCreate } = task; + + this.addLog(onLog, `Generating ${needToCreate} questions for tag "${tag.label}"...`); + + try { + const response = await axios.post(`/api/projects/${projectId}/distill/questions`, { + tagPath, + currentTag: tag.label, + tagId: tag.id, + count: needToCreate, + model, + language + }); + + // 更新生成的问题数量 + if (onProgress) { + onProgress({ + questionsBuilt: response.data.length, + updateType: 'increment' + }); + } + this.addLog(onLog, `Successfully generated ${response.data.length} questions for tag "${tag.label}"`); + } catch (error) { + console.error(`为标签 "${tag.label}" 生成问题失败:`, error); + this.addLog( + onLog, + `Failed to generate questions for tag "${tag.label}": ${error.message || 'Unknown error'}` + ); + } + }) + ); + + // 每完成一批,输出一次进度日志 + this.addLog( + onLog, + `Completed batch ${Math.min(i + concurrencyLimit, generateQuestionTasks.length)}/${generateQuestionTasks.length} of question generation` + ); + } + } catch (error) { + console.error('获取标签失败:', error); + this.addLog(onLog, `Failed to get tags: ${error.message || 'Unknown error'}`); + } + } + + /** + * 为问题生成数据集 + * @param {Object} config - 配置信息 + * @param {string} config.projectId - 项目ID + * @param {Object} config.model - 模型信息 + * @param {string} config.language - 语言 + * @param {Function} config.onProgress - 进度回调 + * @param {Function} config.onLog - 日志回调 + * @returns {Promise} + */ + async generateDatasetsForQuestions(config) { + const { projectId, model, language, concurrencyLimit = 5, onProgress, onLog } = config; + + // 设置当前阶段 + if (onProgress) { + onProgress({ + stage: 'datasets' + }); + } + + this.addLog(onLog, 'Question generation completed, starting to generate answers...'); + + try { + // 获取所有问题 + const response = await axios.get(`/api/projects/${projectId}/questions/tree?isDistill=true`); + const allQuestions = response.data; + + // 找出未回答的问题 + const unansweredQuestions = allQuestions.filter(q => !q.answered); + const answeredQuestions = allQuestions.filter(q => q.answered); + + // 更新总数据集数量和已生成数量 + if (onProgress) { + onProgress({ + datasetsTotal: allQuestions.length, // 总数据集数量应为总问题数量 + datasetsBuilt: answeredQuestions.length // 已生成的数据集数量即已回答的问题数量 + }); + } + + this.addLog(onLog, `Found ${unansweredQuestions.length} unanswered questions, preparing to generate answers...`); + this.addLog(onLog, `Dataset generation concurrency limit: ${concurrencyLimit}`); + + // 分批处理未回答的问题,控制并发数 + for (let i = 0; i < unansweredQuestions.length; i += concurrencyLimit) { + const batch = unansweredQuestions.slice(i, i + concurrencyLimit); + + // 并行处理批次任务 + await Promise.all( + batch.map(async question => { + const questionContent = `${question.label} 下的问题ID:${question.id}`; + this.addLog(onLog, `Generating answer for "${questionContent}"...`); + + try { + // 调用生成数据集的函数 + await this.generateSingleDataset({ + projectId, + questionId: question.id, + questionInfo: question, + model, + language + }); + + // 更新生成的数据集数量 + if (onProgress) { + onProgress({ + datasetsBuilt: 1, + updateType: 'increment' + }); + } + + this.addLog(onLog, `Successfully generated answer for question "${questionContent}"`); + } catch (error) { + console.error(`Failed to generate dataset for question "${question.id}":`, error); + this.addLog( + onLog, + `Failed to generate answer for question "${questionContent}": ${error.message || 'Unknown error'}` + ); + } + }) + ); + + // 每完成一批,输出一次进度日志 + this.addLog( + onLog, + `Completed batch ${Math.min(i + concurrencyLimit, unansweredQuestions.length)}/${unansweredQuestions.length} of dataset generation` + ); + } + + this.addLog(onLog, 'Dataset generation completed'); + } catch (error) { + console.error('Dataset generation failed:', error); + this.addLog(onLog, `Dataset generation error: ${error.message}`); + throw error; + } + } + + /** + * 为问题生成多轮对话数据集 + */ + async generateMultiTurnDatasetsForQuestions(config) { + const { projectId, model, language, concurrencyLimit = 2, onProgress, onLog } = config; + + // 设置当前阶段 + if (onProgress) { + onProgress({ + stage: 'multi-turn-datasets' + }); + } + + this.addLog(onLog, 'Question generation completed, starting to generate multi-turn conversations...'); + + try { + // 获取项目的多轮对话配置 + const configResponse = await axios.get(`/api/projects/${projectId}/tasks`); + const taskConfig = configResponse.data; + + const multiTurnConfig = { + systemPrompt: taskConfig.multiTurnSystemPrompt || '', + scenario: taskConfig.multiTurnScenario || '', + rounds: taskConfig.multiTurnRounds || 3, + roleA: taskConfig.multiTurnRoleA || '', + roleB: taskConfig.multiTurnRoleB || '' + }; + + // 检查是否已配置必要的多轮对话设置 + if ( + !multiTurnConfig.scenario || + !multiTurnConfig.roleA || + !multiTurnConfig.roleB || + !multiTurnConfig.rounds || + multiTurnConfig.rounds < 1 + ) { + throw new Error('项目未配置多轮对话参数,请先在项目设置中配置多轮对话相关参数'); + } + + // 获取所有已回答的问题(多轮对话需要基于已有答案的问题) + const response = await axios.get(`/api/projects/${projectId}/questions/tree?isDistill=true`); + const allQuestions = response.data; + const answeredQuestions = allQuestions; + + if (answeredQuestions.length === 0) { + this.addLog(onLog, 'No answered questions found, skipping multi-turn conversation generation'); + return; + } + + // 获取已生成多轮对话的问题ID + const conversationsResponse = await axios.get(`/api/projects/${projectId}/dataset-conversations?pageSize=1000`); + const existingConversationIds = new Set( + (conversationsResponse.data.conversations || []).map(conv => conv.questionId) + ); + + // 筛选未生成多轮对话的问题 + const questionsForMultiTurn = answeredQuestions.filter(q => !existingConversationIds.has(q.id)); + + // 更新多轮对话数据集总数和已生成数量 + if (onProgress) { + onProgress({ + multiTurnDatasetsTotal: answeredQuestions.length, + multiTurnDatasetsBuilt: answeredQuestions.length - questionsForMultiTurn.length + }); + } + + this.addLog( + onLog, + `Found ${questionsForMultiTurn.length} questions ready for multi-turn conversation generation...` + ); + this.addLog(onLog, `Multi-turn generation concurrency limit: ${concurrencyLimit}`); + + // 分批处理未生成多轮对话的问题,控制并发数 + for (let i = 0; i < questionsForMultiTurn.length; i += concurrencyLimit) { + const batch = questionsForMultiTurn.slice(i, i + concurrencyLimit); + + // 并行处理批次任务 + await Promise.all( + batch.map(async question => { + const questionContent = `${question.label} 下的问题ID:${question.id}`; + this.addLog(onLog, `Generating multi-turn conversation for "${questionContent}"...`); + + try { + // 调用生成多轮对话的函数 + await this.generateSingleMultiTurnDataset({ + projectId, + questionId: question.id, + questionInfo: question, + model, + language, + multiTurnConfig + }); + + // 更新进度 + if (onProgress) { + onProgress({ + multiTurnDatasetsBuilt: 1, + updateType: 'increment' + }); + } + + this.addLog(onLog, `Multi-turn conversation generated for "${questionContent}"`); + } catch (error) { + this.addLog( + onLog, + `Failed to generate multi-turn conversation for "${questionContent}": ${error.message}` + ); + } + }) + ); + } + + this.addLog(onLog, 'Multi-turn conversation generation completed'); + } catch (error) { + console.error('Multi-turn dataset generation failed:', error); + this.addLog(onLog, `Multi-turn dataset generation error: ${error.message}`); + throw error; + } + } + + /** + * 生成单个问题的多轮对话数据集 + */ + async generateSingleMultiTurnDataset({ projectId, questionId, questionInfo, model, language, multiTurnConfig }) { + try { + const response = await axios.post(`/api/projects/${projectId}/dataset-conversations`, { + questionId, + ...multiTurnConfig, + model, + language + }); + + return response.data; + } catch (error) { + console.error('Failed to generate multi-turn dataset:', error); + throw new Error(`Failed to generate multi-turn dataset: ${error.message}`); + } + } + + /** + * 生成单个问题的数据集 + */ + async generateSingleDataset({ projectId, questionId, questionInfo, model, language }) { + try { + // 获取问题信息 + let question = questionInfo; + if (!question) { + const response = await axios.get(`/api/projects/${projectId}/questions/${questionId}`); + question = response.data; + } + + // 生成数据集 + const response = await axios.post(`/api/projects/${projectId}/datasets`, { + projectId, + questionId, + model, + language: language || 'zh-CN' + }); + + return response.data; + } catch (error) { + console.error('Failed to generate dataset:', error); + throw new Error(`Failed to generate dataset: ${error.message}`); + } + } + + /** + * 获取标签深度 + * @param {Object} tag - 标签信息 + * @param {Object} parentMap - 父标签映射 + * @returns {number} - 标签深度 + */ + getTagDepth(tag, parentMap) { + if (!tag) return 0; + + let depth = 1; + let currentTag = tag; + + while (currentTag && currentTag.parentId) { + depth++; + currentTag = parentMap[currentTag.parentId]; + } + + return depth; + } + + /** + * 获取标签路径,确保始终以项目名称开头 + * @param {Object|null} tag - 标签对象 + * @param {Object} parentMap - 父标签映射 + * @returns {string} 标签路径 + */ + getTagPath(tag, parentMap) { + if (!tag) return ''; + + // 使用已经获取的项目名称 + const projectName = this.projectName || ''; + + // 构建标签路径 + const path = []; + let currentTag = tag; + + while (currentTag) { + path.unshift(currentTag.label); + if (currentTag.parentId) { + currentTag = parentMap[currentTag.parentId]; + } else { + currentTag = null; + } + } + + // 确保路径以项目名称开头 + if (projectName && path.length > 0 && path[0] !== projectName) { + path.unshift(projectName); + } + + return path.join(' > '); + } + + /** + * 添加日志 + * @param {Function} onLog - 日志回调 + * @param {string} message - 日志消息 + */ + addLog(onLog, message) { + if (onLog && typeof onLog === 'function') { + onLog(message); + } + } +} + +export const autoDistillService = new AutoDistillService(); +export default autoDistillService; diff --git a/easy-dataset-main/app/projects/[projectId]/distill/page.js b/easy-dataset-main/app/projects/[projectId]/distill/page.js new file mode 100644 index 0000000..eb697e8 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/distill/page.js @@ -0,0 +1,528 @@ +'use client'; + +import React, { useState, useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useParams } from 'next/navigation'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; +import { Box, Typography, Paper, Container, Button, CircularProgress, Alert, IconButton, Tooltip } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import DistillTreeView from '@/components/distill/DistillTreeView'; +import TagGenerationDialog from '@/components/distill/TagGenerationDialog'; +import QuestionGenerationDialog from '@/components/distill/QuestionGenerationDialog'; +import AutoDistillDialog from '@/components/distill/AutoDistillDialog'; +import AutoDistillProgress from '@/components/distill/AutoDistillProgress'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import { autoDistillService } from './autoDistillService'; +import axios from 'axios'; +import { toast } from 'sonner'; + +export default function DistillPage() { + const { t, i18n } = useTranslation(); + const { projectId } = useParams(); + const selectedModel = useAtomValue(selectedModelInfoAtom); + + const [project, setProject] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [tags, setTags] = useState([]); + + // 标签生成对话框相关状态 + const [tagDialogOpen, setTagDialogOpen] = useState(false); + const [questionDialogOpen, setQuestionDialogOpen] = useState(false); + const [selectedTag, setSelectedTag] = useState(null); + const [selectedTagPath, setSelectedTagPath] = useState(''); + + // 自动蒸馏相关状态 + const [autoDistillDialogOpen, setAutoDistillDialogOpen] = useState(false); + const [autoDistillProgressOpen, setAutoDistillProgressOpen] = useState(false); + const [autoDistillRunning, setAutoDistillRunning] = useState(false); + const [distillStats, setDistillStats] = useState({ + tagsCount: 0, + questionsCount: 0, + datasetsCount: 0, + multiTurnDatasetsCount: 0 + }); + const [distillProgress, setDistillProgress] = useState({ + stage: 'initializing', + tagsTotal: 0, + tagsBuilt: 0, + questionsTotal: 0, + questionsBuilt: 0, + datasetsTotal: 0, + datasetsBuilt: 0, + multiTurnDatasetsTotal: 0, // 新增多轮对话数据集总数 + multiTurnDatasetsBuilt: 0, // 新增多轮对话数据集已生成数 + logs: [] + }); + + const treeViewRef = useRef(null); + + // 获取项目信息和标签列表 + useEffect(() => { + if (projectId) { + fetchProject(); + fetchTags(); + fetchDistillStats(); + } + }, [projectId]); + + // 监听多轮对话数据集刷新事件 + useEffect(() => { + const handleRefreshStats = () => { + fetchDistillStats(); + }; + + if (typeof window !== 'undefined') { + window.addEventListener('refreshDistillStats', handleRefreshStats); + + return () => { + window.removeEventListener('refreshDistillStats', handleRefreshStats); + }; + } + }, [projectId]); + + // 获取项目信息 + const fetchProject = async () => { + try { + setLoading(true); + const response = await axios.get(`/api/projects/${projectId}`); + setProject(response.data); + } catch (error) { + console.error('获取项目信息失败:', error); + setError(t('common.fetchError')); + } finally { + setLoading(false); + } + }; + + // 获取标签列表 + const fetchTags = async () => { + try { + setLoading(true); + const response = await axios.get(`/api/projects/${projectId}/distill/tags/all`); + setTags(response.data); + } catch (error) { + console.error('获取标签列表失败:', error); + setError(t('common.fetchError')); + } finally { + setLoading(false); + } + }; + + // 获取蒸馏统计信息 + const fetchDistillStats = async () => { + try { + // 获取标签数量 + const tagsResponse = await axios.get(`/api/projects/${projectId}/distill/tags/all`); + const tagsCount = tagsResponse.data.length; + + // 获取问题数量 + const questionsResponse = await axios.get(`/api/projects/${projectId}/questions/tree?isDistill=true`); + const questionsCount = questionsResponse.data.length; + + // 获取数据集数量 + const datasetsCount = questionsResponse.data.filter(q => q.answered).length; + + // 获取多轮对话数据集数量 + let multiTurnDatasetsCount = 0; + try { + const conversationsResponse = await axios.get( + `/api/projects/${projectId}/dataset-conversations?getAllIds=true` + ); + multiTurnDatasetsCount = (conversationsResponse.data.allConversationIds || []).length; + } catch (error) { + console.log('获取多轮对话数据集统计失败,可能是API不存在:', error.message); + } + + setDistillStats({ + tagsCount, + questionsCount, + datasetsCount, + multiTurnDatasetsCount + }); + } catch (error) { + console.error('获取蒸馏统计信息失败:', error); + } + }; + + // 打开生成标签对话框 + const handleOpenTagDialog = (tag = null, tagPath = '') => { + if (!selectedModel || Object.keys(selectedModel).length === 0) { + setError(t('distill.selectModelFirst')); + return; + } + setSelectedTag(tag); + setSelectedTagPath(tagPath); + setTagDialogOpen(true); + }; + + // 打开生成问题对话框 + const handleOpenQuestionDialog = (tag, tagPath) => { + if (!selectedModel || Object.keys(selectedModel).length === 0) { + setError(t('distill.selectModelFirst')); + return; + } + setSelectedTag(tag); + setSelectedTagPath(tagPath); + setQuestionDialogOpen(true); + }; + + // 处理标签生成完成 + const handleTagGenerated = () => { + fetchTags(); // 重新获取标签列表 + setTagDialogOpen(false); + }; + + // 处理问题生成完成 + const handleQuestionGenerated = () => { + // 关闭对话框 + setQuestionDialogOpen(false); + + // 刷新标签数据 + fetchTags(); + fetchDistillStats(); + + // 如果 treeViewRef 存在且有 fetchQuestionsStats 方法,则调用它刷新问题统计信息 + if (treeViewRef.current && typeof treeViewRef.current.fetchQuestionsStats === 'function') { + treeViewRef.current.fetchQuestionsStats(); + } + }; + + // 打开自动蒸馏对话框 + const handleOpenAutoDistillDialog = () => { + if (!selectedModel || Object.keys(selectedModel).length === 0) { + setError(t('distill.selectModelFirst')); + return; + } + setAutoDistillDialogOpen(true); + }; + + // 开始自动蒸馏任务(前台运行) + const handleStartAutoDistill = async config => { + setAutoDistillDialogOpen(false); + setAutoDistillProgressOpen(true); + setAutoDistillRunning(true); + + // 初始化进度信息 + setDistillProgress({ + stage: 'initializing', + tagsTotal: config.estimatedTags, + tagsBuilt: distillStats.tagsCount || 0, + questionsTotal: config.estimatedQuestions, + questionsBuilt: distillStats.questionsCount || 0, + datasetsTotal: config.estimatedQuestions, // 初步设置数据集总数为问题数,后面会更新 + datasetsBuilt: distillStats.datasetsCount || 0, // 根据当前已生成的数据集数量初始化 + multiTurnDatasetsTotal: + config.datasetType === 'multi-turn' || config.datasetType === 'both' ? config.estimatedQuestions : 0, + multiTurnDatasetsBuilt: distillStats.multiTurnDatasetsCount || 0, + logs: [t('distill.autoDistillStarted', { time: new Date().toLocaleTimeString() })] + }); + + try { + // 检查模型是否存在 + if (!selectedModel || Object.keys(selectedModel).length === 0) { + addLog(t('distill.selectModelFirst')); + setAutoDistillRunning(false); + return; + } + + // 使用 autoDistillService 执行蒸馏任务 + await autoDistillService.executeDistillTask({ + projectId, + topic: config.topic, + levels: config.levels, + tagsPerLevel: config.tagsPerLevel, + questionsPerTag: config.questionsPerTag, + datasetType: config.datasetType, // 新增数据集类型参数 + model: selectedModel, + language: i18n.language, + concurrencyLimit: project?.taskConfig?.concurrencyLimit || 5, // 从项目配置中获取并发限制 + onProgress: updateProgress, + onLog: addLog + }); + + // 更新任务状态 + setAutoDistillRunning(false); + } catch (error) { + console.error('自动蒸馏任务执行失败:', error); + addLog(t('distill.taskExecutionError', { error: error.message || t('common.unknownError') })); + setAutoDistillRunning(false); + } + }; + + // 开始自动蒸馏任务(后台运行) + const handleStartAutoDistillBackground = async config => { + setAutoDistillDialogOpen(false); + + try { + // 检查模型是否存在 + if (!selectedModel || Object.keys(selectedModel).length === 0) { + setError(t('distill.selectModelFirst')); + return; + } + + // 创建后台任务 + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'data-distillation', + modelInfo: selectedModel, + language: i18n.language, + detail: t('distill.autoDistillTaskDetail', { topic: config.topic }), + totalCount: config.estimatedQuestions, + note: { + topic: config.topic, + levels: config.levels, + tagsPerLevel: config.tagsPerLevel, + questionsPerTag: config.questionsPerTag, + datasetType: config.datasetType, + estimatedTags: config.estimatedTags, + estimatedQuestions: config.estimatedQuestions + } + }); + + if (response.data.code === 0) { + toast.success(t('distill.backgroundTaskCreated')); + // 3秒后刷新统计信息 + setTimeout(() => { + fetchDistillStats(); + }, 3000); + } else { + toast.error(response.data.message || t('distill.backgroundTaskFailed')); + } + } catch (error) { + console.error('创建后台蒸馏任务失败:', error); + toast.error(error.message || t('distill.backgroundTaskFailed')); + } + }; + + // 更新进度 + const updateProgress = progressUpdate => { + setDistillProgress(prev => { + const newProgress = { ...prev }; + + // 更新阶段 + if (progressUpdate.stage) { + newProgress.stage = progressUpdate.stage; + } + + // 更新标签总数 + if (progressUpdate.tagsTotal) { + newProgress.tagsTotal = progressUpdate.tagsTotal; + } + + // 更新已构建标签数 + if (progressUpdate.tagsBuilt) { + if (progressUpdate.updateType === 'increment') { + newProgress.tagsBuilt += progressUpdate.tagsBuilt; + } else { + newProgress.tagsBuilt = progressUpdate.tagsBuilt; + } + } + + // 更新问题总数 + if (progressUpdate.questionsTotal) { + newProgress.questionsTotal = progressUpdate.questionsTotal; + } + + // 更新已生成问题数 + if (progressUpdate.questionsBuilt) { + if (progressUpdate.updateType === 'increment') { + newProgress.questionsBuilt += progressUpdate.questionsBuilt; + } else { + newProgress.questionsBuilt = progressUpdate.questionsBuilt; + } + } + + // 更新数据集总数 + if (progressUpdate.datasetsTotal) { + newProgress.datasetsTotal = progressUpdate.datasetsTotal; + } + + // 更新已生成数据集数 + if (progressUpdate.datasetsBuilt) { + if (progressUpdate.updateType === 'increment') { + newProgress.datasetsBuilt += progressUpdate.datasetsBuilt; + } else { + newProgress.datasetsBuilt = progressUpdate.datasetsBuilt; + } + } + + // 更新多轮对话数据集总数 + if (progressUpdate.multiTurnDatasetsTotal) { + newProgress.multiTurnDatasetsTotal = progressUpdate.multiTurnDatasetsTotal; + } + + // 更新已生成多轮对话数据集数 + if (progressUpdate.multiTurnDatasetsBuilt) { + if (progressUpdate.updateType === 'increment') { + newProgress.multiTurnDatasetsBuilt += progressUpdate.multiTurnDatasetsBuilt; + } else { + newProgress.multiTurnDatasetsBuilt = progressUpdate.multiTurnDatasetsBuilt; + } + } + + return newProgress; + }); + }; + + // 添加日志,最多保留200条 + const addLog = message => { + setDistillProgress(prev => { + const newLogs = [...prev.logs, message]; + // 如果日志超过200条,只保留最新的200条 + const limitedLogs = newLogs.length > 200 ? newLogs.slice(-200) : newLogs; + return { + ...prev, + logs: limitedLogs + }; + }); + }; + + // 关闭进度对话框 + const handleCloseProgressDialog = () => { + if (!autoDistillRunning) { + setAutoDistillProgressOpen(false); + // 刷新数据 + fetchTags(); + fetchDistillStats(); + if (treeViewRef.current && typeof treeViewRef.current.fetchQuestionsStats === 'function') { + treeViewRef.current.fetchQuestionsStats(); + } + } else { + // 如果任务还在运行,可以展示一个确认对话框 + // 这里简化处理,直接关闭 + setAutoDistillProgressOpen(false); + } + }; + + if (!projectId) { + return ( + + {t('common.projectIdRequired')} + + ); + } + + return ( + + + + + + {t('distill.title')} + + + { + const helpUrl = + i18n.language === 'en' + ? 'https://docs.easy-dataset.com/ed/en/advanced/images-and-media' + : 'https://docs.easy-dataset.com/jin-jie-shi-yong/images-and-media'; + window.open(helpUrl, '_blank'); + }} + sx={{ color: 'text.secondary' }} + > + + + + + + + + + + + {error && ( + setError('')}> + {error} + + )} + + {loading ? ( + + + + ) : ( + + + + )} + + + {/* 生成标签对话框 */} + {tagDialogOpen && ( + setTagDialogOpen(false)} + onGenerated={handleTagGenerated} + projectId={projectId} + parentTag={selectedTag} + tagPath={selectedTagPath} + model={selectedModel} + /> + )} + + {/* 生成问题对话框 */} + {questionDialogOpen && ( + setQuestionDialogOpen(false)} + onGenerated={handleQuestionGenerated} + projectId={projectId} + tag={selectedTag} + tagPath={selectedTagPath} + model={selectedModel} + /> + )} + + {/* 全自动蒸馏数据集配置对话框 */} + setAutoDistillDialogOpen(false)} + onStart={handleStartAutoDistill} + onStartBackground={handleStartAutoDistillBackground} + projectId={projectId} + project={project} + stats={distillStats} + /> + + {/* 全自动蒸馏进度对话框 */} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/[evalId]/page.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/[evalId]/page.js new file mode 100644 index 0000000..e1695be --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/[evalId]/page.js @@ -0,0 +1,409 @@ +'use client'; +import { useState, useEffect } from 'react'; +import { useParams } from 'next/navigation'; +import { + Box, + Container, + Grid, + Paper, + Chip, + Typography, + CircularProgress, + Alert, + Card, + CardContent, + Divider, + Stack, + RadioGroup, + FormControlLabel, + Radio, + FormGroup, + Checkbox as MuiCheckbox +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useTheme, alpha } from '@mui/material/styles'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import ShortTextIcon from '@mui/icons-material/ShortText'; +import NotesIcon from '@mui/icons-material/Notes'; +import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import TagIcon from '@mui/icons-material/Tag'; +import DescriptionIcon from '@mui/icons-material/Description'; + +import useEvalDatasetDetails from './useEvalDatasetDetails'; +import EvalDatasetHeader from '../components/EvalDatasetHeader'; +import EvalEditableField from '../components/EvalEditableField'; +import TagSelector from '@/components/datasets/TagSelector'; + +// 题型图标和颜色映射 +const QUESTION_TYPE_CONFIG = { + true_false: { + icon: CheckCircleIcon, + color: 'success', + bgColor: 'success.light' + }, + single_choice: { + icon: RadioButtonCheckedIcon, + color: 'primary', + bgColor: 'primary.light' + }, + multiple_choice: { + icon: CheckBoxIcon, + color: 'secondary', + bgColor: 'secondary.light' + }, + short_answer: { + icon: ShortTextIcon, + color: 'warning', + bgColor: 'warning.light' + }, + open_ended: { + icon: NotesIcon, + color: 'info', + bgColor: 'info.light' + } +}; + +export default function EvalDatasetDetailPage() { + const { projectId, evalId } = useParams(); + const { t } = useTranslation(); + const theme = useTheme(); + const [availableTags, setAvailableTags] = useState([]); + + const { data, loading, error, handleNavigate, handleSave, handleDelete } = useEvalDatasetDetails(projectId, evalId); + + // 获取项目中已使用的标签 + useEffect(() => { + const fetchAvailableTags = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/eval-datasets/tags`); + if (response.ok) { + const result = await response.json(); + setAvailableTags(result.tags || []); + } + } catch (error) { + console.error('获取可用标签失败:', error); + } + }; + + if (projectId && !loading) { + fetchAvailableTags(); + } + }, [projectId, loading]); + + if (loading) { + return ( + + + + + + ); + } + + if (error || !data) { + return ( + + {error || t('eval.notFound')} + + ); + } + + const typeConfig = QUESTION_TYPE_CONFIG[data.questionType] || QUESTION_TYPE_CONFIG.short_answer; + const TypeIcon = typeConfig.icon; + + // 解析选项 + let options = []; + try { + options = data.options ? (typeof data.options === 'string' ? JSON.parse(data.options) : data.options) : []; + } catch (e) { + options = []; + } + + // 渲染选项预览 + const renderOptionsPreview = value => { + let opts = []; + try { + opts = value ? (typeof value === 'string' ? JSON.parse(value) : value) : []; + } catch (e) { + return Invalid JSON format; + } + + if (!Array.isArray(opts) || opts.length === 0) { + return {t('common.noData')}; + } + + return ( + + {opts.map((option, index) => { + const optionLabel = String.fromCharCode(65 + index); + const isCorrect = + data.questionType === 'multiple_choice' + ? (Array.isArray(data.correctAnswer) + ? data.correctAnswer + : JSON.parse(data.correctAnswer || '[]') + ).includes(optionLabel) + : data.correctAnswer === optionLabel; + + return ( + + + {optionLabel}. + + + {option} + + {isCorrect && ( + + )} + + ); + })} + + ); + }; + + // 渲染答案编辑组件 + const renderAnswerEditor = (currentValue, onChange) => { + if (data.questionType === 'true_false') { + return ( + onChange(e.target.value)} row> + } label={t('eval.correct')} /> + } label={t('eval.wrong')} /> + + ); + } + + if (data.questionType === 'single_choice') { + return ( + onChange(e.target.value)}> + {options.map((_, index) => { + const label = String.fromCharCode(65 + index); + return ( + } label={`${label}. ${options[index]}`} /> + ); + })} + + ); + } + + if (data.questionType === 'multiple_choice') { + const selected = Array.isArray(currentValue) ? currentValue : JSON.parse(currentValue || '[]'); + const handleChange = label => { + const newSelected = selected.includes(label) ? selected.filter(i => i !== label) : [...selected, label].sort(); + onChange(JSON.stringify(newSelected)); + }; + + return ( + + {options.map((_, index) => { + const label = String.fromCharCode(65 + index); + return ( + handleChange(label)} />} + label={`${label}. ${options[index]}`} + /> + ); + })} + + ); + } + + return null; // 简答题和开放题保持默认文本框 + }; + + return ( + + + + + {/* 左侧主要内容 */} + + + {/* 题型标识 */} + + } + label={t(`eval.questionTypes.${data.questionType}`)} + color={typeConfig.color} + sx={{ + fontWeight: 600, + fontSize: '0.9rem', + py: 0.5, + height: 32 + }} + /> + + + {new Date(data.createAt).toLocaleString()} + + + + {/* 问题 */} + handleSave('question', val)} + placeholder={t('eval.questionPlaceholder')} + /> + + {/* 选项 (仅选择题) */} + {(data.questionType === 'single_choice' || data.questionType === 'multiple_choice') && ( + handleSave('options', val)} + placeholder={'["Option A", "Option B", ...]'} + renderPreview={() => renderOptionsPreview(data.options)} + /> + )} + + {/* 答案 */} + handleSave('correctAnswer', val)} + placeholder={t('eval.answerPlaceholder')} + renderEditor={(val, setVal) => renderAnswerEditor(val, setVal)} + /> + + + + {/* 右侧侧边栏 */} + + + {/* 来源信息 */} + + + + + {t('eval.sourceChunk')} + + {data.chunks ? ( + <> + + {data.chunks.content && ( + + {data.chunks.content} + + )} + + ) : ( + + {t('common.noData')} + + )} + + + + {/* 标签和备注 */} + + + + + + {t('eval.tags')} + + t.trim()) + .filter(Boolean) + : [] + : [] + } + onChange={newTags => handleSave('tags', newTags.join(', '))} + availableTags={availableTags} + placeholder={t('eval.tagsPlaceholder')} + /> + + + + + + + + {t('eval.note')} + + } + value={data.note} + onSave={val => handleSave('note', val)} + placeholder={t('eval.notePlaceholder')} + /> + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/[evalId]/useEvalDatasetDetails.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/[evalId]/useEvalDatasetDetails.js new file mode 100644 index 0000000..0131800 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/[evalId]/useEvalDatasetDetails.js @@ -0,0 +1,149 @@ +'use client'; + +import { useState, useEffect, useCallback, useRef } from 'react'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; +import axios from 'axios'; + +export default function useEvalDatasetDetails(projectId, evalId) { + const router = useRouter(); + const [data, setData] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // 编辑状态 + const [editingField, setEditingField] = useState(null); // 'question', 'options', 'correctAnswer', 'note', 'tags' + const [fieldValue, setFieldValue] = useState(''); + // 获取详情 + const fetchData = useCallback(async () => { + try { + setLoading(true); + setError(null); + const response = await fetch(`/api/projects/${projectId}/eval-datasets/${evalId}`); + + if (!response.ok) { + if (response.status === 404) { + throw new Error('未找到该题目'); + } + throw new Error('获取数据失败'); + } + + const result = await response.json(); + setData(result); + } catch (err) { + console.error(err); + setError(err.message); + } finally { + setLoading(false); + } + }, [projectId, evalId]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + // 导航 + const handleNavigate = async direction => { + try { + const response = await fetch(`/api/projects/${projectId}/eval-datasets/${evalId}?operateType=${direction}`); + if (response.ok) { + const neighbor = await response.json(); + if (neighbor && neighbor.id) { + router.push(`/projects/${projectId}/eval-datasets/${neighbor.id}`); + } else { + toast.warning(`已经是${direction === 'next' ? '最后' : '第'}一条数据了`); + } + } + } catch (err) { + console.error('Navigation error:', err); + } + }; + + // 开始编辑 + const handleStartEdit = (field, value) => { + setEditingField(field); + // 对于 options,如果是数组则转为 JSON 字符串编辑,或者在组件层面处理 + // 这里假设 value 已经是适合编辑的格式 + setFieldValue(value); + }; + + // 取消编辑 + const handleCancelEdit = () => { + setEditingField(null); + setFieldValue(''); + }; + + // 保存编辑 + const handleSave = async (field, value) => { + try { + const response = await fetch(`/api/projects/${projectId}/eval-datasets/${evalId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ [field]: value }) + }); + + if (!response.ok) throw new Error('保存失败'); + + const updated = await response.json(); + setData(prev => ({ ...prev, ...updated })); // 更新本地数据 + setEditingField(null); + toast.success('保存成功'); + } catch (err) { + toast.error(err.message); + } + }; + + // 删除 + const handleDelete = async () => { + if (!confirm('确定要删除这条数据吗?此操作不可撤销。')) return; + + try { + // 先尝试获取下一条,以便删除后跳转 + const nextResponse = await fetch(`/api/projects/${projectId}/eval-datasets/${evalId}?operateType=next`); + let nextId = null; + if (nextResponse.ok) { + const next = await nextResponse.json(); + if (next && next.id) nextId = next.id; + } + + // 如果没有下一条,尝试获取上一条 + if (!nextId) { + const prevResponse = await fetch(`/api/projects/${projectId}/eval-datasets/${evalId}?operateType=prev`); + if (prevResponse.ok) { + const prev = await prevResponse.json(); + if (prev && prev.id) nextId = prev.id; + } + } + + // 删除 + const deleteResponse = await fetch(`/api/projects/${projectId}/eval-datasets/${evalId}`, { + method: 'DELETE' + }); + + if (!deleteResponse.ok) throw new Error('删除失败'); + + toast.success('删除成功'); + + if (nextId) { + router.replace(`/projects/${projectId}/eval-datasets/${nextId}`); + } else { + router.push(`/projects/${projectId}/eval-datasets`); + } + } catch (err) { + toast.error(err.message); + } + }; + return { + data, + loading, + error, + editingField, + fieldValue, + setFieldValue, + handleNavigate, + handleStartEdit, + handleCancelEdit, + handleSave, + handleDelete + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/BuiltinDatasetDialog.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/BuiltinDatasetDialog.js new file mode 100644 index 0000000..12dd07d --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/BuiltinDatasetDialog.js @@ -0,0 +1,327 @@ +'use client'; + +import { useState, useMemo } from 'react'; +import { + Dialog, + DialogContent, + DialogActions, + Button, + Box, + Typography, + TextField, + Card, + CardActionArea, + Chip, + IconButton, + Tooltip, + InputAdornment, + CircularProgress, + DialogTitle, + DialogContentText +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import SearchIcon from '@mui/icons-material/Search'; +import StorageIcon from '@mui/icons-material/Storage'; +import { useTranslation } from 'react-i18next'; +import { alpha, useTheme } from '@mui/material/styles'; +import { StyledDialogTitle } from './ImportDialog.styles'; +import { DATA_SETS } from '../constants'; + +export default function BuiltinDatasetDialog({ open, onClose, projectId, onSuccess }) { + const { t, i18n } = useTranslation(); + const theme = useTheme(); + const [keyword, setKeyword] = useState(''); + const [selectedDataset, setSelectedDataset] = useState(null); + const [confirmOpen, setConfirmOpen] = useState(false); + const [downloading, setDownloading] = useState(false); + + const isZh = i18n.language.startsWith('zh'); + + // 过滤数据集 + const filteredDatasets = useMemo(() => { + if (!keyword) return DATA_SETS; + const lowerKeyword = keyword.toLowerCase(); + return DATA_SETS.filter( + ds => + ds.zh.toLowerCase().includes(lowerKeyword) || + ds.en.toLowerCase().includes(lowerKeyword) || + ds.type.toLowerCase().includes(lowerKeyword) + ); + }, [keyword]); + + const handleCardClick = dataset => { + setSelectedDataset(dataset); + setConfirmOpen(true); + }; + + const handleConfirmClose = () => { + setConfirmOpen(false); + setSelectedDataset(null); + }; + + const handleImport = async () => { + if (!selectedDataset) return; + + setDownloading(true); + setConfirmOpen(false); + + try { + const cdnUrl = `https://raw.githubusercontent.com/ConardLi/easy-dataset-eval/main/${selectedDataset.file}`; + const response = await fetch(cdnUrl); + if (!response.ok) { + throw new Error(`Failed to fetch dataset: ${response.statusText}`); + } + const jsonData = await response.blob(); + + const formData = new FormData(); + const file = new File([jsonData], `${selectedDataset.en}.json`, { type: 'application/json' }); + formData.append('file', file); + formData.append('questionType', selectedDataset.type); + const tags = `[${selectedDataset.level}] ${selectedDataset.en}`; + formData.append('tags', tags); + + const importResponse = await fetch(`/api/projects/${projectId}/eval-datasets/import`, { + method: 'POST', + body: formData + }); + + const result = await importResponse.json(); + + if (result.code === 0) { + onSuccess?.(result.data); + handleClose(); + } else { + console.error(result.error); + alert(result.error || t('evalDatasets.import.failed')); + } + } catch (error) { + console.error('Import failed:', error); + alert(error.message || t('evalDatasets.import.failed')); + } finally { + setDownloading(false); + setSelectedDataset(null); + } + }; + + const handleClose = () => { + if (downloading) return; + setKeyword(''); + setSelectedDataset(null); + setConfirmOpen(false); + onClose(); + }; + + return ( + <> + + + + + + {t('evalDatasets.import.builtinTitle', '选择内置数据集')} + + + + + + + + + {/* 搜索栏 */} + + setKeyword(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + sx: { borderRadius: 2 } + }} + /> + + + {/* 数据集列表 */} + + {downloading ? ( + + + + {t('evalDatasets.import.downloading', '下载并导入中...')} + + + ) : ( + + {filteredDatasets.map((ds, index) => { + const difficultyColor = ds.level === 'easy' ? 'success.main' : 'warning.main'; + const typeLabel = t(`eval.questionTypes.${ds.type}`, ds.type); + const tooltipTitle = ( + + + + + ); + + return ( + + handleCardClick(ds)} + > + + + {isZh ? ds.zh : ds.en} + + + + + ); + })} + + )} + + + + + + + {t('evalDatasets.import.confirmImportTitle', '确认导入')} + + + + {selectedDataset && + t('evalDatasets.import.confirmImportMessage', { + name: isZh ? selectedDataset.zh : selectedDataset.en + })} + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetCard.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetCard.js new file mode 100644 index 0000000..026fd0b --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetCard.js @@ -0,0 +1,335 @@ +'use client'; + +import { Card, CardContent, Box, Typography, Chip, Checkbox, IconButton, Tooltip, Divider } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import ShortTextIcon from '@mui/icons-material/ShortText'; +import NotesIcon from '@mui/icons-material/Notes'; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; +import { useTheme, alpha } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/navigation'; + +// 题型图标和颜色映射 +const QUESTION_TYPE_CONFIG = { + true_false: { + icon: CheckCircleIcon, + color: 'success', + bgColor: 'success.light' + }, + single_choice: { + icon: RadioButtonCheckedIcon, + color: 'primary', + bgColor: 'primary.light' + }, + multiple_choice: { + icon: CheckBoxIcon, + color: 'secondary', + bgColor: 'secondary.light' + }, + short_answer: { + icon: ShortTextIcon, + color: 'warning', + bgColor: 'warning.light' + }, + open_ended: { + icon: NotesIcon, + color: 'info', + bgColor: 'info.light' + } +}; + +export default function EvalDatasetCard({ item, selected, onSelect, onEdit, onDelete, projectId }) { + const theme = useTheme(); + const { t } = useTranslation(); + const router = useRouter(); + + const typeConfig = QUESTION_TYPE_CONFIG[item.questionType] || QUESTION_TYPE_CONFIG.short_answer; + const TypeIcon = typeConfig.icon; + + // 解析选项 + const options = item.options + ? typeof item.options === 'string' + ? JSON.parse(item.options || '[]') + : item.options + : []; + + // 解析答案 + const correctAnswer = item.correctAnswer; + + const handleCardClick = e => { + // 如果点击的是复选框或按钮,不跳转 + if (e.target.closest('.MuiCheckbox-root') || e.target.closest('.MuiIconButton-root')) { + return; + } + router.push(`/projects/${projectId}/eval-datasets/${item.id}`); + }; + + return ( + + + {/* 头部:题型标签和操作 */} + + + { + e.stopPropagation(); + onSelect(item.id); + }} + sx={{ p: 0.5, ml: -0.5 }} + /> + } + label={t(`eval.questionTypes.${item.questionType}`)} + size="small" + color={typeConfig.color} + variant="outlined" + sx={{ + fontWeight: 600, + borderWidth: '1.5px', + bgcolor: alpha(theme.palette[typeConfig.color].main, 0.05) + }} + /> + + + + { + e.stopPropagation(); + onEdit(item); + }} + sx={{ + color: 'text.secondary', + '&:hover': { color: 'primary.main', bgcolor: alpha(theme.palette.primary.main, 0.1) } + }} + > + + + + + { + e.stopPropagation(); + onDelete(item.id); + }} + sx={{ + color: 'text.secondary', + '&:hover': { color: 'error.main', bgcolor: alpha(theme.palette.error.main, 0.1) } + }} + > + + + + + + + {/* 问题内容 */} + + + {item.questionType === 'true_false' && correctAnswer} {item.question} + + + + {/* 选项列表(仅单选/多选显示) */} + {(item.questionType === 'single_choice' || item.questionType === 'multiple_choice') && options.length > 0 && ( + + {(item.questionType === 'multiple_choice' ? options : options.slice(0, 4)).map((option, index) => { + const optionLabel = String.fromCharCode(65 + index); // A, B, C, D + // 解析多选题答案,支持多种格式:数组、JSON字符串、逗号分隔字符串 + const parseMultipleAnswers = answer => { + if (Array.isArray(answer)) return answer; + if (!answer) return []; + // 尝试解析 JSON 数组 + if (answer.startsWith('[')) { + try { + return JSON.parse(answer); + } catch (e) { + return []; + } + } + // 逗号分隔字符串格式,如 "A,B,D" + return answer.split(',').map(s => s.trim()); + }; + const isCorrect = + item.questionType === 'multiple_choice' + ? parseMultipleAnswers(correctAnswer).includes(optionLabel) + : correctAnswer === optionLabel; + + return ( + + + {optionLabel}. + + + {option} + + + ); + })} + {item.questionType === 'single_choice' && options.length > 4 && ( + + ... +{options.length - 4} {t('eval.moreOptions')} + + )} + + )} + + {/* 非选择题且非判断题答案 */} + {item.questionType !== 'single_choice' && + item.questionType !== 'multiple_choice' && + item.questionType !== 'true_false' && + correctAnswer && ( + + + {t('eval.answer')}: + + + {correctAnswer} + + + )} + + + + {/* 底部元信息 */} + + {item.chunks ? ( + + + + ) : ( + + )} + + {item.tags && ( + + + {item.tags + .split(/[,,]/) + .slice(0, 2) + .map((tag, index) => ( + + ))} + {item.tags.split(/[,,]/).length > 2 && ( + + +{item.tags.split(/[,,]/).length - 2} + + )} + + + )} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetHeader.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetHeader.js new file mode 100644 index 0000000..4fc41f5 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetHeader.js @@ -0,0 +1,44 @@ +'use client'; + +import { Box, Button, Divider, Typography, IconButton, Paper, Tooltip } from '@mui/material'; +import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/navigation'; + +export default function EvalDatasetHeader({ projectId, onNavigate, onDelete }) { + const router = useRouter(); + const { t } = useTranslation(); + + return ( + + + + + + {t('eval.detail')} + + + + onNavigate('prev')} title={t('common.prev')}> + + + onNavigate('next')} title={t('common.next')}> + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetList.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetList.js new file mode 100644 index 0000000..5f4ae23 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalDatasetList.js @@ -0,0 +1,155 @@ +'use client'; + +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Checkbox, + IconButton, + Chip, + Typography, + Tooltip, + Box +} from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import { useTranslation } from 'react-i18next'; + +export default function EvalDatasetList({ items, selectedIds, onSelect, onSelectAll, onEdit, onDelete, onView }) { + const { t } = useTranslation(); + + const isAllSelected = items.length > 0 && selectedIds.length === items.length; + const isIndeterminate = selectedIds.length > 0 && selectedIds.length < items.length; + + // 题型颜色映射 + const getTypeColor = type => { + const colors = { + true_false: 'success', + single_choice: 'primary', + multiple_choice: 'secondary', + short_answer: 'warning', + open_ended: 'info' + }; + return colors[type] || 'default'; + }; + + // 格式化答案显示 + const formatAnswer = item => { + const { questionType, correctAnswer, options } = item; + + if (questionType === 'true_false') { + return correctAnswer; + } + + if (questionType === 'single_choice' || questionType === 'multiple_choice') { + return correctAnswer; + } + + // 非选择题,截断显示 + if (correctAnswer && correctAnswer.length > 50) { + return correctAnswer.substring(0, 50) + '...'; + } + return correctAnswer || '-'; + }; + + return ( + + + + + + + + {t('eval.questionType')} + {t('eval.question')} + {t('eval.answer')} + {t('eval.sourceChunk')} + + {t('common.actions')} + + + + + {items.map(item => ( + + + onSelect(item.id)} /> + + + + + + + {item.question} + + + + + {formatAnswer(item)} + + + + {item.chunks ? ( + + ) : ( + + - + + )} + + + + + onView(item)}> + + + + + onDelete(item.id)}> + + + + + + + ))} + {items.length === 0 && ( + + + {t('common.noData')} + + + )} + +
+
+ ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalEditableField.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalEditableField.js new file mode 100644 index 0000000..4aab2fd --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalEditableField.js @@ -0,0 +1,126 @@ +'use client'; + +import { useState } from 'react'; +import { Box, Typography, Button, TextField, IconButton, Paper } from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import SaveIcon from '@mui/icons-material/Save'; +import CancelIcon from '@mui/icons-material/Cancel'; +import { useTheme, alpha } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; + +export default function EvalEditableField({ + label, + value, + multiline = true, + onSave, + placeholder, + renderPreview, // Optional custom preview renderer + renderEditor // Optional custom editor renderer (currentValue, onChange) => ReactNode +}) { + const { t } = useTranslation(); + const theme = useTheme(); + const [editing, setEditing] = useState(false); + const [editValue, setEditValue] = useState(''); + + const handleStartEdit = () => { + setEditValue(value || ''); + setEditing(true); + }; + + const handleCancel = () => { + setEditing(false); + setEditValue(''); + }; + + const handleSave = async () => { + if (onSave) { + await onSave(editValue); + } + setEditing(false); + }; + + return ( + + + + {label} + + {!editing && ( + + + + )} + + + {editing ? ( + + {renderEditor && renderEditor(editValue, setEditValue) ? ( + {renderEditor(editValue, setEditValue)} + ) : ( + setEditValue(e.target.value)} + placeholder={placeholder} + variant="outlined" + size="small" + sx={{ mb: 2 }} + /> + )} + + + + + + ) : ( + + {renderPreview ? ( + renderPreview(value) + ) : ( + + {value || t('common.noData')} + + )} + + )} + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalToolbar.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalToolbar.js new file mode 100644 index 0000000..404ec97 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalToolbar.js @@ -0,0 +1,260 @@ +'use client'; + +import { + Box, + IconButton, + ToggleButton, + Tooltip, + Divider, + Autocomplete, + TextField, + Menu, + MenuItem +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import ViewModuleIcon from '@mui/icons-material/ViewModule'; +import ViewListIcon from '@mui/icons-material/ViewList'; +import DeleteIcon from '@mui/icons-material/DeleteOutline'; // 使用 Outline 版本更精致 +import CheckCircleIcon from '@mui/icons-material/CheckCircleOutline'; // 统一使用 Outline 风格图标 +import RadioButtonCheckedIcon from '@mui/icons-material/RadioButtonChecked'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import CheckBoxIcon from '@mui/icons-material/CheckBoxOutlineBlank'; // 或者 CheckBox +import ShortTextIcon from '@mui/icons-material/ShortText'; +import NotesIcon from '@mui/icons-material/Notes'; +import UploadFileIcon from '@mui/icons-material/UploadFile'; +import StorageIcon from '@mui/icons-material/Storage'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import { useTranslation } from 'react-i18next'; +import { useTheme, alpha } from '@mui/material/styles'; +import { useState } from 'react'; + +import { + ToolbarContainer, + FilterGroup, + FilterButton, + SearchWrapper, + StyledInputBase, + ActionGroup, + ActionButton, + DeleteActionButton, + StyledToggleButtonGroup +} from './EvalToolbar.styles'; + +const STATS_CONFIG = [ + { key: 'true_false', icon: CheckCircleIcon, color: 'success' }, + { key: 'single_choice', icon: RadioButtonCheckedIcon, color: 'primary' }, + { key: 'multiple_choice', icon: CheckBoxIcon, color: 'secondary' }, + { key: 'short_answer', icon: ShortTextIcon, color: 'warning' }, + { key: 'open_ended', icon: NotesIcon, color: 'info' } +]; + +export default function EvalToolbar({ + keyword, + onKeywordChange, + viewMode, + onViewModeChange, + selectedCount, + onDeleteSelected, + stats, + questionType, + onTypeChange, + tags, + onTagsChange, + onImport, + onBuiltinImport, + onExport +}) { + const { t } = useTranslation(); + const theme = useTheme(); + + const [importAnchorEl, setImportAnchorEl] = useState(null); + + const handleImportClick = event => { + setImportAnchorEl(event.currentTarget); + }; + + const handleImportClose = () => { + setImportAnchorEl(null); + }; + + const handleCustomImport = () => { + handleImportClose(); + onImport?.(); + }; + + const handleBuiltinImport = () => { + handleImportClose(); + onBuiltinImport?.(); + }; + + const tagOptions = stats?.byTag + ? Object.keys(stats.byTag).map(tag => ({ + label: tag, + count: stats.byTag[tag] + })) + : []; + + return ( + + {/* 顶部:题型统计筛选 */} + + {stats && + STATS_CONFIG.map(({ key, icon: Icon, color }) => { + const count = stats.byType?.[key] || 0; + const isActive = questionType === key; + + return ( + } + active={isActive} + colorType={color} + onClick={() => onTypeChange(isActive ? '' : key)} + > + {t(`eval.questionTypes.${key}`)} + + ({count}) + + + ); + })} + + + + + {/* 底部:筛选和操作 */} + + {/* 左侧:筛选器组 */} + + {/* 搜索框 */} + + + + + onKeywordChange(e.target.value)} + /> + + + {/* 标签筛选 */} + `${option.label} (${option.count})`} + value={tagOptions.filter(o => tags.includes(o.label))} + onChange={(e, newValue) => onTagsChange(newValue.map(v => v.label))} + renderInput={params => ( + + )} + sx={{ + '& .MuiAutocomplete-tag': { + height: 24, + borderRadius: 1 + } + }} + /> + + + {/* 右侧:操作按钮组 */} + + {/* 导入按钮下拉菜单 */} + } + endIcon={} + onClick={handleImportClick} + > + {t('common.import', '导入')} + + + + + {t('evalDatasets.import.custom', '导入自定义数据集')} + + + + {t('evalDatasets.import.builtin', '导入内置数据集')} + + + + {/* 导出按钮 */} + } onClick={onExport}> + {t('common.export', '导出')} + + + {selectedCount > 0 && ( + } onClick={onDeleteSelected}> + {t('eval.deleteSelectedCount', `删除选中 (${selectedCount})`, { count: selectedCount })} + + )} + + + + value && onViewModeChange(value)} + size="small" + > + + + + + + + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalToolbar.styles.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalToolbar.styles.js new file mode 100644 index 0000000..4f0cfa3 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/EvalToolbar.styles.js @@ -0,0 +1,151 @@ +import { styled, alpha } from '@mui/material/styles'; +import { Box, Paper, Button, ToggleButton, ToggleButtonGroup, InputBase } from '@mui/material'; + +export const ToolbarContainer = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(2, 2.5), + marginBottom: theme.spacing(3), + borderRadius: theme.shape.borderRadius * 2, + border: `1px solid ${theme.palette.divider}`, + backgroundColor: theme.palette.background.paper, + boxShadow: '0 2px 12px rgba(0,0,0,0.03)', + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(2) +})); + +export const FilterGroup = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1.5), + flexWrap: 'wrap' +})); + +export const FilterButton = styled(Button, { + shouldForwardProp: prop => prop !== 'active' && prop !== 'colorType' +})(({ theme, active, colorType }) => { + const colorMap = { + success: theme.palette.success, + primary: theme.palette.primary, + secondary: theme.palette.secondary, + warning: theme.palette.warning, + info: theme.palette.info + }; + const mainColor = colorMap[colorType] || theme.palette.primary; + + return { + padding: theme.spacing(0.75, 2), + borderRadius: theme.shape.borderRadius * 5, // Pill shape + border: '1px solid', + borderColor: active ? mainColor.main : theme.palette.divider, + backgroundColor: active ? alpha(mainColor.main, 0.1) : 'transparent', + color: active ? mainColor.main : theme.palette.text.secondary, + fontSize: '0.875rem', + fontWeight: active ? 600 : 400, + minWidth: 'auto', + textTransform: 'none', + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + backgroundColor: active ? alpha(mainColor.main, 0.15) : alpha(theme.palette.text.primary, 0.04), + borderColor: active ? mainColor.main : theme.palette.text.secondary, + transform: 'translateY(-1px)' + }, + '& .MuiButton-startIcon': { + marginRight: theme.spacing(0.8), + color: active ? mainColor.main : theme.palette.text.disabled, + width: 18, + height: 18 + } + }; +}); + +export const SearchWrapper = styled(Paper)(({ theme }) => ({ + padding: '2px 4px', + display: 'flex', + alignItems: 'center', + width: 280, + height: 42, + borderRadius: theme.shape.borderRadius * 1.5, + border: `1px solid ${theme.palette.divider}`, + backgroundColor: theme.palette.background.paper, + boxShadow: 'none', + transition: 'all 0.2s ease', + '&:hover': { + borderColor: theme.palette.text.secondary, + backgroundColor: alpha(theme.palette.action.hover, 0.05) + }, + '&:focus-within': { + borderColor: theme.palette.primary.main, + boxShadow: `0 0 0 3px ${alpha(theme.palette.primary.main, 0.1)}`, + backgroundColor: theme.palette.background.paper + } +})); + +export const StyledInputBase = styled(InputBase)(({ theme }) => ({ + marginLeft: theme.spacing(1), + flex: 1, + fontSize: '0.875rem', + '& input': { + '&::placeholder': { + color: theme.palette.text.disabled, + opacity: 1 + } + } +})); + +export const ActionGroup = styled(Box)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1.5) +})); + +export const ActionButton = styled(Button)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius * 1.5, + height: 40, + paddingLeft: theme.spacing(3), + paddingRight: theme.spacing(3), + borderColor: theme.palette.divider, + color: theme.palette.text.secondary, + '&:hover': { + borderColor: theme.palette.text.primary, + color: theme.palette.text.primary, + backgroundColor: theme.palette.action.hover + } +})); + +export const DeleteActionButton = styled(Button)(({ theme }) => ({ + borderRadius: theme.shape.borderRadius * 1.5, + height: 40, + paddingLeft: theme.spacing(2), + paddingRight: theme.spacing(2), + backgroundColor: alpha(theme.palette.error.main, 0.1), + color: theme.palette.error.main, + '&:hover': { + backgroundColor: alpha(theme.palette.error.main, 0.2) + } +})); + +export const StyledToggleButtonGroup = styled(ToggleButtonGroup)(({ theme }) => ({ + height: 40, + backgroundColor: theme.palette.action.hover, // Slightly darker than paper + padding: 4, + borderRadius: theme.shape.borderRadius * 1.5, + border: 'none', + gap: 4, + '& .MuiToggleButton-root': { + border: 'none', + borderRadius: theme.shape.borderRadius, + width: 36, + color: theme.palette.text.secondary, + '&.Mui-selected': { + backgroundColor: theme.palette.background.paper, + color: theme.palette.primary.main, + boxShadow: '0 2px 4px rgba(0,0,0,0.05)', + '&:hover': { + backgroundColor: theme.palette.background.paper + } + }, + '&:hover': { + backgroundColor: 'rgba(0,0,0,0.04)' + } + } +})); diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ExportEvalDialog.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ExportEvalDialog.js new file mode 100644 index 0000000..04d6e9e --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ExportEvalDialog.js @@ -0,0 +1,259 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Chip, + OutlinedInput, + Checkbox, + ListItemText, + Alert, + CircularProgress, + IconButton, + ToggleButton, + ToggleButtonGroup, + Divider +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import FilterAltIcon from '@mui/icons-material/FilterAlt'; +import ClearIcon from '@mui/icons-material/Clear'; +import { useTranslation } from 'react-i18next'; + +const QUESTION_TYPES = [ + { value: 'true_false', labelKey: 'eval.questionTypes.true_false' }, + { value: 'single_choice', labelKey: 'eval.questionTypes.single_choice' }, + { value: 'multiple_choice', labelKey: 'eval.questionTypes.multiple_choice' }, + { value: 'short_answer', labelKey: 'eval.questionTypes.short_answer' }, + { value: 'open_ended', labelKey: 'eval.questionTypes.open_ended' } +]; + +const EXPORT_FORMATS = [ + { value: 'json', label: 'JSON', description: 'evalDatasets.export.jsonDesc' }, + { value: 'jsonl', label: 'JSONL', description: 'evalDatasets.export.jsonlDesc' }, + { value: 'csv', label: 'CSV', description: 'evalDatasets.export.csvDesc' } +]; + +export default function ExportEvalDialog({ + open, + onClose, + exporting, + error, + format, + setFormat, + questionTypes, + setQuestionTypes, + selectedTags, + setSelectedTags, + keyword, + setKeyword, + previewTotal, + previewLoading, + availableTags, + resetFilters, + onExport +}) { + const { t } = useTranslation(); + + const hasFilters = questionTypes.length > 0 || selectedTags.length > 0 || keyword; + + return ( + + + + + + {t('evalDatasets.export.title', '导出评估数据集')} + + + + + + + + + {error && ( + {}}> + {error} + + )} + + {/* 导出格式选择 */} + + + {t('evalDatasets.export.formatLabel', '导出格式')} + + newFormat && setFormat(newFormat)} + fullWidth + size="small" + > + {EXPORT_FORMATS.map(f => ( + + + + {f.label} + + + {t(f.description, f.label)} + + + + ))} + + + + + + {/* 筛选条件 */} + + + + + {t('evalDatasets.export.filterLabel', '筛选条件')} + + {hasFilters && ( + + )} + + + + {/* 关键字搜索 */} + setKeyword(e.target.value)} + /> + + {/* 题型和标签筛选 */} + + {/* 题型筛选 */} + + {t('evalTasks.filterByTypeLabel', '题型筛选')} + + + + {/* 标签筛选 */} + + {t('evalTasks.filterByTagLabel', '标签筛选')} + + + + + + + {/* 导出预览 */} + + + {t('evalDatasets.export.previewLabel', '将导出数据:')} + + {previewLoading ? ( + + ) : ( + + {previewTotal} {t('evalDatasets.export.records', '条记录')} + + )} + + + {previewTotal > 1000 && ( + + {t('evalDatasets.export.largeDataHint', '数据量较大,将采用流式导出,请耐心等待')} + + )} + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ImportDialog.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ImportDialog.js new file mode 100644 index 0000000..66114d6 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ImportDialog.js @@ -0,0 +1,319 @@ +'use client'; + +import { useState, useRef } from 'react'; +import { + Dialog, + DialogContent, + DialogActions, + Button, + Box, + Typography, + TextField, + Alert, + LinearProgress, + Chip, + IconButton, + Radio +} from '@mui/material'; +import CloudUploadIcon from '@mui/icons-material/CloudUpload'; +import DownloadIcon from '@mui/icons-material/Download'; +import CloseIcon from '@mui/icons-material/Close'; +import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import { useTranslation } from 'react-i18next'; +import * as XLSX from 'xlsx'; + +import { + QUESTION_TYPES, + FORMAT_PREVIEW, + getJsonTemplateData, + getExcelTemplateData, + getColumnWidths +} from '../constants'; +import { + StyledDialogTitle, + UploadBox, + PreviewPaper, + CodeBlock, + ErrorContainer, + TypeRadioGroup, + TypeFormControlLabel +} from './ImportDialog.styles'; + +export default function ImportDialog({ open, onClose, projectId, onSuccess }) { + const { t } = useTranslation(); + const fileInputRef = useRef(null); + + const [questionType, setQuestionType] = useState('open_ended'); + const [tags, setTags] = useState(''); + const [file, setFile] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [errorDetails, setErrorDetails] = useState([]); + + // 处理文件选择 + const handleFileChange = e => { + const selectedFile = e.target.files[0]; + if (selectedFile) { + const ext = selectedFile.name.split('.').pop().toLowerCase(); + if (!['json', 'xls', 'xlsx'].includes(ext)) { + setError(t('evalDatasets.import.invalidFileType', '不支持的文件格式,请上传 json、xls 或 xlsx 文件')); + return; + } + setFile(selectedFile); + setError(null); + setErrorDetails([]); + } + }; + + // 下载模板 + const handleDownloadTemplate = format => { + if (!questionType) { + setError(t('evalDatasets.import.selectTypeFirst', '请先选择题型')); + return; + } + + if (format === 'json') { + // JSON 模板动态生成并下载 + const templateData = getJsonTemplateData(questionType); + const jsonContent = JSON.stringify(templateData, null, 2); + const blob = new Blob([jsonContent], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `eval-dataset-template-${questionType}.json`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + } else { + // Excel 模板动态生成 + const templateData = getExcelTemplateData(questionType); + const worksheet = XLSX.utils.json_to_sheet(templateData); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, 'Template'); + + // 设置列宽 + const colWidths = getColumnWidths(questionType); + worksheet['!cols'] = colWidths; + + // 下载文件 + XLSX.writeFile(workbook, `eval-dataset-template-${questionType}.xlsx`); + } + }; + + // 提交导入 + const handleSubmit = async () => { + if (!questionType) { + setError(t('evalDatasets.import.selectTypeFirst', '请先选择题型')); + return; + } + if (!file) { + setError(t('evalDatasets.import.selectFile', '请选择要导入的文件')); + return; + } + + setLoading(true); + setError(null); + setErrorDetails([]); + + try { + const formData = new FormData(); + formData.append('file', file); + formData.append('questionType', questionType); + formData.append('tags', tags); + + const response = await fetch(`/api/projects/${projectId}/eval-datasets/import`, { + method: 'POST', + body: formData + }); + + const result = await response.json(); + + if (result.code === 0) { + onSuccess?.(result.data); + handleClose(); + } else { + setError(result.error || result.message); + if (result.details) { + setErrorDetails(result.details); + } + } + } catch (err) { + setError(err.message || t('evalDatasets.import.failed', '导入失败')); + } finally { + setLoading(false); + } + }; + + // 关闭对话框 + const handleClose = () => { + if (loading) return; + setQuestionType('open_ended'); + setTags(''); + setFile(null); + setError(null); + setErrorDetails([]); + onClose(); + }; + + // 获取当前题型的格式预览 + const formatPreview = questionType ? FORMAT_PREVIEW[questionType] : null; + + return ( + + + {t('evalDatasets.import.title', '导入评估数据集')} + + + + + + + {loading && } + + {/* 错误提示 */} + {error && ( + + {error} + {errorDetails.length > 0 && ( + + {errorDetails.map((detail, index) => ( + + {detail} + + ))} + {errorDetails.length < 10 && ( + + {t('evalDatasets.import.showingErrors', '显示前 {{count}} 条错误', { count: errorDetails.length })} + + )} + + )} + + )} + + {/* 题型选择 - 使用封装好的样式组件 */} + + + {t('evalDatasets.import.questionType', '选择题型')} + + setQuestionType(e.target.value)}> + {QUESTION_TYPES.map(type => ( + } + label={t(type.label, type.labelZh)} + /> + ))} + + + + {/* 数据格式预览 */} + {formatPreview && ( + + + {t('evalDatasets.import.formatPreview', '数据格式预览')} + + + {formatPreview.fields.map(field => ( + + ))} + + + {formatPreview.description} + + +
{JSON.stringify(formatPreview.example, null, 2)}
+
+ + {/* 下载模板按钮 */} + + + + +
+ )} + + {/* 文件上传 */} + + + fileInputRef.current?.click()}> + {file ? ( + + + + {file.name} + + + + {t('common.clickToReplace', '点击更换文件')} + + + ) : ( + + + + {t('evalDatasets.import.dropOrClick', '点击或拖拽文件到此处')} + + + {t('evalDatasets.import.supportedFormats', '支持 JSON、XLS、XLSX 格式')} + + + )} + + + + {/* 标签输入 */} + setTags(e.target.value)} + disabled={loading} + helperText={t('evalDatasets.import.tagsHelp', '导入的所有数据将打上这些标签')} + InputProps={{ + startAdornment: tags ? # : null + }} + /> +
+ + + + + +
+ ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ImportDialog.styles.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ImportDialog.styles.js new file mode 100644 index 0000000..a34dfc4 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/components/ImportDialog.styles.js @@ -0,0 +1,133 @@ +import { styled, alpha } from '@mui/material/styles'; +import { Box, Paper, DialogTitle as MuiDialogTitle, RadioGroup, FormControlLabel } from '@mui/material'; + +export const StyledDialogTitle = styled(MuiDialogTitle)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + padding: theme.spacing(2, 3), + borderBottom: `1px solid ${theme.palette.divider}`, + '& .MuiTypography-root': { + fontWeight: 600, + fontSize: '1.1rem' + } +})); + +export const TypeRadioGroup = styled(RadioGroup)(({ theme }) => ({ + display: 'flex', + flexDirection: 'row', + gap: theme.spacing(2) +})); + +export const TypeFormControlLabel = styled(FormControlLabel, { + shouldForwardProp: prop => prop !== 'checked' +})(({ theme, checked }) => ({ + margin: 0, + padding: '4px 12px', + borderRadius: '8px', + border: '1px solid', + borderColor: checked ? theme.palette.primary.main : theme.palette.divider, + backgroundColor: checked ? alpha(theme.palette.primary.main, 0.05) : 'transparent', + transition: 'all 0.2s', + '&:hover': { + backgroundColor: checked ? alpha(theme.palette.primary.main, 0.08) : theme.palette.action.hover + }, + '& .MuiTypography-root': { + fontSize: '0.875rem', + color: checked ? theme.palette.primary.main : theme.palette.text.primary, + fontWeight: checked ? 600 : 400 + }, + '& .MuiRadio-root': { + padding: '4px', + color: checked ? theme.palette.primary.main : theme.palette.text.secondary + } +})); + +export const UploadBox = styled(Box, { + shouldForwardProp: prop => prop !== 'active' && prop !== 'hasFile' +})(({ theme, active, hasFile }) => ({ + border: '2px dashed', + borderColor: active ? theme.palette.primary.main : theme.palette.grey[300], + borderRadius: theme.shape.borderRadius * 2, + padding: theme.spacing(4), + textAlign: 'center', + cursor: 'pointer', + backgroundColor: active + ? alpha(theme.palette.primary.main, 0.05) + : hasFile + ? alpha(theme.palette.primary.main, 0.05) + : 'transparent', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + borderColor: theme.palette.primary.main, + backgroundColor: alpha(theme.palette.primary.main, 0.02), + transform: 'translateY(-1px)', + boxShadow: '0 4px 12px rgba(0,0,0,0.05)' + }, + '& svg': { + fontSize: 48, + marginBottom: theme.spacing(1), + color: active ? theme.palette.primary.main : theme.palette.grey[400], + transition: 'color 0.3s ease' + } +})); + +export const PreviewPaper = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(2.5), + marginBottom: theme.spacing(3), + backgroundColor: theme.palette.grey[50], + border: `1px solid ${theme.palette.divider}`, + borderRadius: theme.shape.borderRadius * 1.5, + '& .title': { + display: 'flex', + alignItems: 'center', + gap: theme.spacing(1), + marginBottom: theme.spacing(1.5), + color: theme.palette.text.primary, + fontWeight: 600 + } +})); + +export const CodeBlock = styled(Box)(({ theme }) => ({ + backgroundColor: '#1e1e1e', // Dark theme for code + color: '#d4d4d4', + padding: theme.spacing(2), + borderRadius: theme.shape.borderRadius, + fontFamily: '"Fira Code", "Roboto Mono", monospace', + fontSize: '0.85rem', + overflow: 'auto', + maxHeight: 300, + '&::-webkit-scrollbar': { + height: 8, + width: 8 + }, + '&::-webkit-scrollbar-track': { + backgroundColor: '#2d2d2d' + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: '#555', + borderRadius: 4 + } +})); + +export const ErrorContainer = styled(Box)(({ theme }) => ({ + marginTop: theme.spacing(1), + fontSize: '0.85rem', + maxHeight: 200, + overflowY: 'auto', + '& .item': { + padding: theme.spacing(0.5, 0), + color: theme.palette.error.main, + display: 'flex', + alignItems: 'flex-start', + gap: theme.spacing(1), + '&::before': { + content: '"•"', + fontWeight: 'bold' + } + } +})); + +export const TagInputWrapper = styled(Box)(({ theme }) => ({ + // Custom styles for tag input area if needed +})); diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/constants.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/constants.js new file mode 100644 index 0000000..5cc087b --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/constants.js @@ -0,0 +1,679 @@ +export const QUESTION_TYPES = [ + { value: 'true_false', label: 'eval.questionTypes.true_false', labelZh: '判断题' }, + { value: 'single_choice', label: 'eval.questionTypes.single_choice', labelZh: '单选题' }, + { value: 'multiple_choice', label: 'eval.questionTypes.multiple_choice', labelZh: '多选题' }, + { value: 'short_answer', label: 'eval.questionTypes.short_answer', labelZh: '短答案题' }, + { value: 'open_ended', label: 'eval.questionTypes.open_ended', labelZh: '开放式问题' } +]; + +export const FORMAT_PREVIEW = { + true_false: { + fields: ['question', 'correctAnswer'], + example: { + question: 'Artificial Intelligence is a branch of computer science', + correctAnswer: '✅ or ❌' + }, + description: 'correctAnswer must be "✅" (correct) or "❌" (incorrect)' + }, + single_choice: { + fields: ['question', 'options', 'correctAnswer'], + example: { + question: 'Which of the following is a core feature of deep learning?', + options: '["Option A", "Option B", "Option C", "Option D"]', + correctAnswer: 'B' + }, + description: 'options is an array of options, correctAnswer is the letter of the correct option (A/B/C/D)' + }, + multiple_choice: { + fields: ['question', 'options', 'correctAnswer'], + example: { + question: 'Which of the following are commonly used deep learning frameworks?', + options: '["TensorFlow", "PyTorch", "Excel", "Keras"]', + correctAnswer: '["A", "B", "D"]' + }, + description: 'options is an array of options, correctAnswer is an array of correct option letters' + }, + short_answer: { + fields: ['question', 'correctAnswer'], + example: { + question: 'What is the typical model structure used in deep learning?', + correctAnswer: 'Neural Network' + }, + description: 'correctAnswer is a short standard answer' + }, + open_ended: { + fields: ['question', 'correctAnswer'], + example: { + question: 'Analyze the main reasons for the success of deep learning in computer vision.', + correctAnswer: 'Reference answer content...' + }, + description: 'correctAnswer is a reference answer (can be long)' + } +}; + +// 获取 JSON 模板数据 +export const getJsonTemplateData = type => { + switch (type) { + case 'true_false': + return [ + { question: 'Artificial Intelligence is a branch of computer science', correctAnswer: '✅' }, + { question: 'Deep learning does not require large amounts of data for training', correctAnswer: '❌' } + ]; + case 'single_choice': + return [ + { + question: 'What is the core feature of deep learning?', + options: [ + 'Requires manual feature engineering', + 'Automatic feature learning', + 'Only handles structured data', + 'Does not need large amounts of data' + ], + correctAnswer: 'B' + }, + { + question: 'Which of the following is a commonly used deep learning framework?', + options: ['Excel', 'Word', 'TensorFlow', 'PowerPoint'], + correctAnswer: 'C' + } + ]; + case 'multiple_choice': + return [ + { + question: 'Which of the following are commonly used deep learning frameworks?', + options: ['TensorFlow', 'PyTorch', 'Excel', 'Keras', 'Word'], + correctAnswer: ['A', 'B', 'D'] + }, + { + question: 'Which of the following are main types of machine learning?', + options: ['Supervised Learning', 'Unsupervised Learning', 'Reinforcement Learning', 'Manual Learning'], + correctAnswer: ['A', 'B', 'C'] + } + ]; + case 'short_answer': + return [ + { question: 'What is the typical model structure used in deep learning?', correctAnswer: 'Neural Network' }, + { question: 'What is the maximum sample size mentioned in the text?', correctAnswer: '1000' } + ]; + case 'open_ended': + return [ + { + question: 'Analyze the main reasons for the success of deep learning in computer vision.', + correctAnswer: + 'The success of deep learning in computer vision can be explained from three dimensions: models, data, and computing power...' + }, + { + question: 'Explain the overfitting problem in machine learning and its solutions.', + correctAnswer: + 'Overfitting refers to the phenomenon where a model performs well on training data but poorly on new data...' + } + ]; + default: + return []; + } +}; + +// 获取 Excel 模板数据 +export const getExcelTemplateData = type => { + switch (type) { + case 'true_false': + return [ + { question: 'Artificial Intelligence is a branch of computer science', correctAnswer: '✅' }, + { question: 'Deep learning does not require large amounts of data for training', correctAnswer: '❌' } + ]; + case 'single_choice': + return [ + { + question: 'What is the core feature of deep learning?', + options: `["Requires manual feature engineering", "Automatic feature learning", "Only handles structured data", "Does not need large amounts of data"]`, + correctAnswer: 'B' + }, + { + question: 'Which of the following is a commonly used deep learning framework?', + options: `["Excel", "Word", "TensorFlow", "PowerPoint"]`, + correctAnswer: 'C' + } + ]; + case 'multiple_choice': + return [ + { + question: 'Which of the following are commonly used deep learning frameworks?', + options: `["TensorFlow", "PyTorch", "Excel", "Keras", "Word"]`, + correctAnswer: `["A", "B", "D"]` + }, + { + question: 'Which of the following are main types of machine learning?', + options: `["Supervised Learning", "Unsupervised Learning", "Reinforcement Learning", "Manual Learning"]`, + correctAnswer: `["A", "B", "C"]` + } + ]; + case 'short_answer': + return [ + { question: 'What is the typical model structure used in deep learning?', correctAnswer: 'Neural Network' }, + { question: 'What is the maximum sample size mentioned in the text?', correctAnswer: '1000' } + ]; + case 'open_ended': + return [ + { + question: 'Analyze the main reasons for the success of deep learning in computer vision.', + correctAnswer: + 'The success of deep learning in computer vision can be explained from three dimensions: models, data, and computing power...' + }, + { + question: 'Explain the overfitting problem in machine learning and its solutions.', + correctAnswer: + 'Overfitting refers to the phenomenon where a model performs well on training data but poorly on new data...' + } + ]; + default: + return []; + } +}; + +// 获取列宽配置 +export const getColumnWidths = type => { + if (type === 'single_choice' || type === 'multiple_choice') { + return [{ wch: 50 }, { wch: 25 }, { wch: 25 }, { wch: 25 }, { wch: 25 }, { wch: 25 }, { wch: 15 }]; + } + return [{ wch: 60 }, { wch: 40 }]; +}; + +export const DATA_SETS = [ + { + zh: '生物学', + en: 'Biology', + file: 'mmlu-pro/biology.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '商业', + en: 'Business', + file: 'mmlu-pro/business.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '化学', + en: 'Chemistry', + file: 'mmlu-pro/chemistry.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '计算机科学', + en: 'Computer Science', + file: 'mmlu-pro/computer_science.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '经济学', + en: 'Economics', + file: 'mmlu-pro/economics.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '工程学', + en: 'Engineering', + file: 'mmlu-pro/engineering.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '健康科学', + en: 'Health', + file: 'mmlu-pro/health.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '历史', + en: 'History', + file: 'mmlu-pro/history.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '法律', + en: 'Law', + file: 'mmlu-pro/law.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '数学', + en: 'Math', + file: 'mmlu-pro/math.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '其他', + en: 'Other', + file: 'mmlu-pro/other.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '哲学', + en: 'Philosophy', + file: 'mmlu-pro/philosophy.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '物理', + en: 'Physics', + file: 'mmlu-pro/physics.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '心理学', + en: 'Psychology', + file: 'mmlu-pro/psychology.json', + level: 'hard', + type: 'single_choice' + }, + { + zh: '抽象代数', + en: 'Abstract Algebra', + file: 'mmlu/abstract_algebra_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '解剖学', + en: 'Anatomy', + file: 'mmlu/anatomy_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '天文学', + en: 'Astronomy', + file: 'mmlu/astronomy_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '商业伦理', + en: 'Business Ethics', + file: 'mmlu/business_ethics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '临床知识', + en: 'Clinical Knowledge', + file: 'mmlu/clinical_knowledge_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '大学生物', + en: 'College Biology', + file: 'mmlu/college_biology_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '大学化学', + en: 'College Chemistry', + file: 'mmlu/college_chemistry_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '大学计算机科学', + en: 'College Computer Science', + file: 'mmlu/college_computer_science_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '大学数学', + en: 'College Mathematics', + file: 'mmlu/college_mathematics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '大学医学', + en: 'College Medicine', + file: 'mmlu/college_medicine_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '大学物理', + en: 'College Physics', + file: 'mmlu/college_physics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '计算机安全', + en: 'Computer Security', + file: 'mmlu/computer_security_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '概念物理', + en: 'Conceptual Physics', + file: 'mmlu/conceptual_physics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '计量经济学', + en: 'Econometrics', + file: 'mmlu/econometrics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '电气工程', + en: 'Electrical Engineering', + file: 'mmlu/electrical_engineering_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '初等数学', + en: 'Elementary Mathematics', + file: 'mmlu/elementary_mathematics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '形式逻辑', + en: 'Formal Logic', + file: 'mmlu/formal_logic_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '全球事实', + en: 'Global Facts', + file: 'mmlu/global_facts_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中生物', + en: 'High School Biology', + file: 'mmlu/high_school_biology_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中化学', + en: 'High School Chemistry', + file: 'mmlu/high_school_chemistry_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中计算机科学', + en: 'High School Computer Science', + file: 'mmlu/high_school_computer_science_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中欧洲历史', + en: 'High School European History', + file: 'mmlu/high_school_european_history_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中地理', + en: 'High School Geography', + file: 'mmlu/high_school_geography_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中政府与政治', + en: 'High School Government And Politics', + file: 'mmlu/high_school_government_and_politics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中宏观经济学', + en: 'High School Macroeconomics', + file: 'mmlu/high_school_macroeconomics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中数学', + en: 'High School Mathematics', + file: 'mmlu/high_school_mathematics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中微观经济学', + en: 'High School Microeconomics', + file: 'mmlu/high_school_microeconomics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中物理', + en: 'High School Physics', + file: 'mmlu/high_school_physics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中心理学', + en: 'High School Psychology', + file: 'mmlu/high_school_psychology_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中统计学', + en: 'High School Statistics', + file: 'mmlu/high_school_statistics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中美国历史', + en: 'High School Us History', + file: 'mmlu/high_school_us_history_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '高中世界历史', + en: 'High School World History', + file: 'mmlu/high_school_world_history_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '人类衰老', + en: 'Human Aging', + file: 'mmlu/human_aging_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '人类性学', + en: 'Human Sexuality', + file: 'mmlu/human_sexuality_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '国际法', + en: 'International Law', + file: 'mmlu/international_law_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '法理学', + en: 'Jurisprudence', + file: 'mmlu/jurisprudence_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '逻辑谬误', + en: 'Logical Fallacies', + file: 'mmlu/logical_fallacies_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '机器学习', + en: 'Machine Learning', + file: 'mmlu/machine_learning_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '管理学', + en: 'Management', + file: 'mmlu/management_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '市场营销', + en: 'Marketing', + file: 'mmlu/marketing_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '医学遗传学', + en: 'Medical Genetics', + file: 'mmlu/medical_genetics_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '杂项/综合', + en: 'Miscellaneous', + file: 'mmlu/miscellaneous_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '道德争议', + en: 'Moral Disputes', + file: 'mmlu/moral_disputes_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '道德场景', + en: 'Moral Scenarios', + file: 'mmlu/moral_scenarios_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '营养学', + en: 'Nutrition', + file: 'mmlu/nutrition_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '哲学', + en: 'Philosophy', + file: 'mmlu/philosophy_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '史前史', + en: 'Prehistory', + file: 'mmlu/prehistory_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '专业会计', + en: 'Professional Accounting', + file: 'mmlu/professional_accounting_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '专业法律', + en: 'Professional Law', + file: 'mmlu/professional_law_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '专业医学', + en: 'Professional Medicine', + file: 'mmlu/professional_medicine_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '专业心理学', + en: 'Professional Psychology', + file: 'mmlu/professional_psychology_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '公共关系', + en: 'Public Relations', + file: 'mmlu/public_relations_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '安全研究', + en: 'Security Studies', + file: 'mmlu/security_studies_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '社会学', + en: 'Sociology', + file: 'mmlu/sociology_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '美国外交政策', + en: 'Us Foreign Policy', + file: 'mmlu/us_foreign_policy_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '病毒学', + en: 'Virology', + file: 'mmlu/virology_test.json', + level: 'easy', + type: 'single_choice' + }, + { + zh: '世界宗教测试', + en: 'World Religions', + file: 'mmlu/world_religions_test.json', + level: 'easy', + type: 'single_choice' + } +]; diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/hooks/useEvalDatasets.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/hooks/useEvalDatasets.js new file mode 100644 index 0000000..401b743 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/hooks/useEvalDatasets.js @@ -0,0 +1,220 @@ +'use client'; + +import { useState, useCallback, useEffect, useRef } from 'react'; + +/** + * Eval datasets list hook + * @param {string} projectId + */ +export default function useEvalDatasets(projectId) { + const [data, setData] = useState({ items: [], total: 0, stats: null, totalPages: 1 }); + const [loading, setLoading] = useState(true); + const [searching, setSearching] = useState(false); + const [error, setError] = useState(null); + const isInitialMount = useRef(true); + const abortRef = useRef(null); + + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(20); + + const [questionType, setQuestionType] = useState(''); + const [keyword, setKeyword] = useState(''); + const [debouncedKeyword, setDebouncedKeyword] = useState(''); + const [chunkId, setChunkId] = useState(''); + const [tags, setTags] = useState([]); + + const setQuestionTypeWithReset = useCallback(value => { + setQuestionType(value); + setPage(1); + }, []); + + const setKeywordWithReset = useCallback(value => { + setKeyword(value); + }, []); + + const setChunkIdWithReset = useCallback(value => { + setChunkId(value); + setPage(1); + }, []); + + const setTagsWithReset = useCallback(value => { + setTags(value); + setPage(1); + }, []); + + const [viewMode, setViewMode] = useState('card'); + const [selectedIds, setSelectedIds] = useState([]); + + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedKeyword(keyword); + if (keyword !== debouncedKeyword) { + setPage(1); + } + }, 500); + + return () => clearTimeout(timer); + }, [keyword]); + + const fetchDataRef = useRef(null); + fetchDataRef.current = async (showLoading = true, options = {}) => { + if (!projectId) return; + + const includeStats = options.forceStats || showLoading; + + if (abortRef.current) { + abortRef.current.abort(); + } + const controller = new AbortController(); + abortRef.current = controller; + + if (showLoading) { + setLoading(true); + } else { + setSearching(true); + } + setError(null); + + try { + const params = new URLSearchParams({ + page: String(page), + pageSize: String(pageSize), + includeStats: includeStats ? 'true' : 'false' + }); + + if (questionType) params.append('questionType', questionType); + if (debouncedKeyword) params.append('keyword', debouncedKeyword); + if (chunkId) params.append('chunkId', chunkId); + if (tags.length > 0) { + tags.forEach(tag => params.append('tags', tag)); + } + + const response = await fetch(`/api/projects/${projectId}/eval-datasets?${params}`, { + signal: controller.signal + }); + + if (!response.ok) { + throw new Error('Failed to fetch eval datasets'); + } + + const result = await response.json(); + setData(prev => ({ + ...result, + stats: result.stats ?? prev.stats + })); + } catch (err) { + if (err?.name === 'AbortError') return; + setError(err.message); + } finally { + if (abortRef.current === controller) { + abortRef.current = null; + } + + if (showLoading) { + setLoading(false); + } else { + setSearching(false); + } + } + }; + + const fetchData = useCallback((showLoading = true, options = {}) => { + return fetchDataRef.current?.(showLoading, options); + }, []); + + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false; + fetchDataRef.current?.(true, { forceStats: true }); + } else { + fetchDataRef.current?.(false, { forceStats: false }); + } + }, [projectId, page, pageSize, questionType, debouncedKeyword, chunkId, tags]); + + useEffect(() => { + return () => { + if (abortRef.current) { + abortRef.current.abort(); + } + }; + }, []); + + const deleteItems = useCallback( + async ids => { + if (!ids || ids.length === 0) return; + + const response = await fetch(`/api/projects/${projectId}/eval-datasets`, { + method: 'DELETE', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ids }) + }); + + if (!response.ok) { + throw new Error('Failed to delete items'); + } + + await fetchData(true, { forceStats: true }); + setSelectedIds([]); + + return await response.json(); + }, + [projectId, fetchData] + ); + + const resetFilters = useCallback(() => { + setQuestionType(''); + setKeyword(''); + setChunkId(''); + setTags([]); + setPage(1); + }, []); + + const toggleSelect = useCallback(id => { + setSelectedIds(prev => (prev.includes(id) ? prev.filter(i => i !== id) : [...prev, id])); + }, []); + + const toggleSelectAll = useCallback(() => { + if (selectedIds.length === data.items.length) { + setSelectedIds([]); + } else { + setSelectedIds(data.items.map(item => item.id)); + } + }, [selectedIds, data.items]); + + return { + items: data.items, + total: data.total, + stats: data.stats, + totalPages: data.totalPages || 1, + + loading, + searching, + error, + + page, + pageSize, + setPage, + setPageSize, + + questionType, + keyword, + chunkId, + tags, + setQuestionType: setQuestionTypeWithReset, + setKeyword: setKeywordWithReset, + setChunkId: setChunkIdWithReset, + setTags: setTagsWithReset, + resetFilters, + + viewMode, + setViewMode, + + selectedIds, + toggleSelect, + toggleSelectAll, + setSelectedIds, + + fetchData, + deleteItems + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/hooks/useExportEvalDatasets.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/hooks/useExportEvalDatasets.js new file mode 100644 index 0000000..70d7f4f --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/hooks/useExportEvalDatasets.js @@ -0,0 +1,189 @@ +'use client'; + +import { useState, useCallback, useEffect } from 'react'; + +/** + * 评估数据集导出 Hook + * 管理导出对话框状态、筛选条件和导出逻辑 + */ +export default function useExportEvalDatasets(projectId, stats = {}) { + // 对话框状态 + const [dialogOpen, setDialogOpen] = useState(false); + const [exporting, setExporting] = useState(false); + const [error, setError] = useState(''); + + // 导出配置 + const [format, setFormat] = useState('json'); + const [questionTypes, setQuestionTypes] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); + const [keyword, setKeyword] = useState(''); + + // 预览数据 + const [previewTotal, setPreviewTotal] = useState(0); + const [previewLoading, setPreviewLoading] = useState(false); + + // 从 stats 中获取可用的标签列表 + const availableTags = stats?.byTag ? Object.keys(stats.byTag).sort() : []; + + // 当筛选条件变化时,获取预览数量 + useEffect(() => { + if (!dialogOpen || !projectId) return; + + const controller = new AbortController(); + + const fetchPreview = async () => { + try { + setPreviewLoading(true); + const params = new URLSearchParams(); + + if (questionTypes.length > 0) { + questionTypes.forEach(t => params.append('questionTypes', t)); + } + if (selectedTags.length > 0) { + selectedTags.forEach(t => params.append('tags', t)); + } + if (keyword.trim()) { + params.append('keyword', keyword.trim()); + } + + const response = await fetch(`/api/projects/${projectId}/eval-datasets/export?${params.toString()}`, { + signal: controller.signal + }); + + if (response.ok) { + const result = await response.json(); + setPreviewTotal(result?.data?.total ?? 0); + } + } catch (err) { + if (err.name !== 'AbortError') { + console.error('获取导出预览失败:', err); + } + } finally { + setPreviewLoading(false); + } + }; + + fetchPreview(); + + return () => { + controller.abort(); + }; + }, [dialogOpen, projectId, questionTypes, selectedTags, keyword]); + + // 打开对话框 + const openDialog = useCallback(() => { + setDialogOpen(true); + setError(''); + }, []); + + // 关闭对话框 + const closeDialog = useCallback(() => { + if (exporting) return; + setDialogOpen(false); + // 重置状态 + setFormat('json'); + setQuestionTypes([]); + setSelectedTags([]); + setKeyword(''); + setError(''); + }, [exporting]); + + // 重置筛选条件 + const resetFilters = useCallback(() => { + setQuestionTypes([]); + setSelectedTags([]); + setKeyword(''); + }, []); + + // 执行导出 + const handleExport = useCallback(async () => { + if (previewTotal === 0) { + setError('没有符合条件的数据可导出'); + return; + } + + try { + setExporting(true); + setError(''); + + const response = await fetch(`/api/projects/${projectId}/eval-datasets/export`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + format, + questionTypes, + tags: selectedTags, + keyword: keyword.trim() + }) + }); + + if (!response.ok) { + const result = await response.json(); + throw new Error(result.error || '导出失败'); + } + + // 获取文件名 + const contentDisposition = response.headers.get('Content-Disposition'); + let filename = `eval-datasets-${Date.now()}.${format}`; + if (contentDisposition) { + const match = contentDisposition.match(/filename="?([^"]+)"?/); + if (match) { + filename = match[1]; + } + } + + // 下载文件 + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + window.URL.revokeObjectURL(url); + + // 导出成功,关闭对话框 + closeDialog(); + + return true; + } catch (err) { + console.error('导出失败:', err); + setError(err.message || '导出失败'); + return false; + } finally { + setExporting(false); + } + }, [projectId, format, questionTypes, selectedTags, keyword, previewTotal, closeDialog]); + + return { + // 对话框状态 + dialogOpen, + openDialog, + closeDialog, + + // 导出状态 + exporting, + error, + setError, + + // 导出配置 + format, + setFormat, + questionTypes, + setQuestionTypes, + selectedTags, + setSelectedTags, + keyword, + setKeyword, + + // 预览数据 + previewTotal, + previewLoading, + availableTags, + + // 操作 + resetFilters, + handleExport + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-datasets/page.js b/easy-dataset-main/app/projects/[projectId]/eval-datasets/page.js new file mode 100644 index 0000000..4ee328b --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-datasets/page.js @@ -0,0 +1,322 @@ +'use client'; + +import { useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { + Box, + Container, + Typography, + Grid, + Pagination, + CircularProgress, + Alert, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Snackbar +} from '@mui/material'; +import { Masonry } from '@mui/lab'; +import { useTranslation } from 'react-i18next'; + +import useEvalDatasets from './hooks/useEvalDatasets'; +import useExportEvalDatasets from './hooks/useExportEvalDatasets'; +import EvalToolbar from './components/EvalToolbar'; +import EvalDatasetCard from './components/EvalDatasetCard'; +import EvalDatasetList from './components/EvalDatasetList'; +import ImportDialog from './components/ImportDialog'; +import BuiltinDatasetDialog from './components/BuiltinDatasetDialog'; +import ExportEvalDialog from './components/ExportEvalDialog'; + +export default function EvalDatasetsPage() { + const { projectId } = useParams(); + const router = useRouter(); + const { t } = useTranslation(); + + const { + items, + total, + stats, + totalPages, + loading, + searching, + error, + page, + setPage, + questionType, + setQuestionType, + tags, + setTags, + keyword, + setKeyword, + viewMode, + setViewMode, + selectedIds, + toggleSelect, + toggleSelectAll, + fetchData, + deleteItems + } = useEvalDatasets(projectId); + + // 导出 Hook + const { + dialogOpen: exportDialogOpen, + openDialog: openExportDialog, + closeDialog: closeExportDialog, + exporting, + error: exportError, + format: exportFormat, + setFormat: setExportFormat, + questionTypes: exportQuestionTypes, + setQuestionTypes: setExportQuestionTypes, + selectedTags: exportSelectedTags, + setSelectedTags: setExportSelectedTags, + keyword: exportKeyword, + setKeyword: setExportKeyword, + previewTotal, + previewLoading, + availableTags: exportAvailableTags, + resetFilters: resetExportFilters, + handleExport + } = useExportEvalDatasets(projectId, stats); + + // 删除确认对话框 + const [deleteDialog, setDeleteDialog] = useState({ open: false, ids: [] }); + + // 导入对话框 + const [importDialogOpen, setImportDialogOpen] = useState(false); + const [builtinImportOpen, setBuiltinImportOpen] = useState(false); + + // Toast 提示 + const [toast, setToast] = useState({ open: false, message: '', severity: 'success' }); + + // 处理导入成功 + const handleImportSuccess = result => { + setToast({ + open: true, + message: t('evalDatasets.import.successMessage', { count: result.total }), + severity: 'success' + }); + fetchData(); // 刷新数据 + }; + + // 处理删除 + const handleDelete = async ids => { + setDeleteDialog({ open: true, ids: Array.isArray(ids) ? ids : [ids] }); + }; + + const confirmDelete = async () => { + try { + await deleteItems(deleteDialog.ids); + setDeleteDialog({ open: false, ids: [] }); + } catch (err) { + console.error('Delete failed:', err); + } + }; + + // 处理编辑 + const handleEdit = item => { + router.push(`/projects/${projectId}/eval-datasets/${item.id}`); + }; + + // 处理查看 + const handleView = item => { + router.push(`/projects/${projectId}/eval-datasets/${item.id}`); + }; + + return ( + + {/* 错误提示 */} + {error && ( + + {error} + + )} + + {/* 工具栏(包含统计筛选) */} + handleDelete(selectedIds)} + stats={stats} + questionType={questionType} + onTypeChange={setQuestionType} + tags={tags} + onTagsChange={setTags} + onRefresh={fetchData} + loading={loading} + onImport={() => setImportDialogOpen(true)} + onBuiltinImport={() => setBuiltinImportOpen(true)} + onExport={openExportDialog} + /> + + {/* 加载状态 */} + {loading && ( + + + + )} + + {/* 内容区域 */} + {!loading && ( + + {/* 搜索加载遮罩 */} + {searching && ( + + + + )} + + {viewMode === 'card' ? ( + + + {items.map(item => ( + + ))} + + + ) : ( + + + + )} + + {/* 空状态 */} + {items.length === 0 && ( + + + {t('eval.noData')} + + + {t('eval.noDataHint')} + + + )} + + {/* 分页 */} + {totalPages > 1 && ( + + setPage(value)} + color="primary" + showFirstButton + showLastButton + /> + + )} + + )} + + {/* 删除确认对话框 */} + setDeleteDialog({ open: false, ids: [] })}> + {t('eval.deleteConfirmTitle')} + + {t('eval.deleteConfirmMessage', { count: deleteDialog.ids.length })} + + + + + + + + {/* 导入对话框 */} + setImportDialogOpen(false)} + projectId={projectId} + onSuccess={handleImportSuccess} + /> + + {/* 内置数据集导入对话框 */} + setBuiltinImportOpen(false)} + projectId={projectId} + onSuccess={handleImportSuccess} + /> + + {/* 导出对话框 */} + + + {/* Toast 提示 */} + setToast({ ...toast, open: false })} + anchorOrigin={{ vertical: 'top', horizontal: 'center' }} + > + setToast({ ...toast, open: false })}> + {toast.message} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/EvalHeader.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/EvalHeader.js new file mode 100644 index 0000000..904810f --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/EvalHeader.js @@ -0,0 +1,140 @@ +'use client'; + +import { Box, Paper, Typography, Chip, Grid, Divider } from '@mui/material'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import { detailStyles } from '../detailStyles'; +import { useTranslation } from 'react-i18next'; +import { getModelIcon } from '@/lib/util/modelIcon'; + +export default function EvalHeader({ task, stats, filterCorrect, onFilterCorrectSelect }) { + const { t } = useTranslation(); + + if (!task) return null; + + const { modelInfo, createAt, status, detail } = task; + const score = detail?.finalScore || 0; + const isPass = score >= 60; + const totalTime = task.endTime ? Math.floor((new Date(task.endTime) - new Date(task.createAt)) / 1000) : 0; + + const incorrectCount = (stats?.totalQuestions || 0) - (stats?.correctCount || 0); + + // 获取教师模型信息 + const judgeModelId = detail?.judgeModelId; + const judgeProviderId = detail?.judgeProviderId; + const hasJudgeModel = judgeModelId && judgeProviderId; + + return ( + + + {/* 左侧:模型信息 */} + + + {modelInfo?.modelId + + + + {modelInfo?.providerName || modelInfo?.providerId} / {modelInfo?.modelName || modelInfo?.modelId} + + + {hasJudgeModel && ( + + )} + + + {new Date(createAt).toLocaleString()} + {totalTime > 0 && ` ${t('evalTasks.durationFormat', { time: totalTime })}`} + + + + + + {/* 中间:统计概览 (增加点击筛选) */} + + onFilterCorrectSelect(null)} + sx={{ + ...detailStyles.statBox, + cursor: 'pointer', + bgcolor: filterCorrect === null ? 'rgba(25, 118, 210, 0.08)' : 'background.default', + border: filterCorrect === null ? '1px solid' : '1px solid transparent', + borderColor: 'primary.main', + transition: 'all 0.2s' + }} + > + + {stats?.totalQuestions || 0} + + + {t('evalTasks.totalQuestionsLabel')} + + + onFilterCorrectSelect(true)} + sx={{ + ...detailStyles.statBox, + cursor: 'pointer', + bgcolor: filterCorrect === true ? 'rgba(46, 125, 50, 0.08)' : 'background.default', + border: filterCorrect === true ? '1px solid' : '1px solid transparent', + borderColor: 'success.main', + transition: 'all 0.2s' + }} + > + + {stats?.correctCount || 0} + + + {t('evalTasks.correctLabel')} + + + onFilterCorrectSelect(false)} + sx={{ + ...detailStyles.statBox, + cursor: 'pointer', + bgcolor: filterCorrect === false ? 'rgba(211, 47, 47, 0.08)' : 'background.default', + border: filterCorrect === false ? '1px solid' : '1px solid transparent', + borderColor: 'error.main', + transition: 'all 0.2s' + }} + > + + {incorrectCount} + + + {t('evalTasks.incorrectLabel')} + + + + + {/* 右侧:分数印章 */} + + {score.toFixed(1)} + SCORE + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/EvalStats.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/EvalStats.js new file mode 100644 index 0000000..ce7ed08 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/EvalStats.js @@ -0,0 +1,95 @@ +'use client'; + +import { Box, Grid, Typography, LinearProgress } from '@mui/material'; +import { detailStyles } from '../detailStyles'; +import { useTranslation } from 'react-i18next'; + +const QUESTION_TYPE_LABELS = { + true_false: 'eval.questionTypes.true_false', + single_choice: 'eval.questionTypes.single_choice', + multiple_choice: 'eval.questionTypes.multiple_choice', + short_answer: 'eval.questionTypes.short_answer', + open_ended: 'eval.questionTypes.open_ended' +}; + +export default function EvalStats({ stats, currentFilter, onFilterSelect }) { + const { t } = useTranslation(); + + if (!stats?.byType || Object.keys(stats.byType).length === 0) return null; + + return ( + + + {Object.entries(stats.byType).map(([type, typeStats]) => { + const accuracy = typeStats.total > 0 ? (typeStats.correct / typeStats.total) * 100 : 0; + + const isSelected = currentFilter === type; + + return ( + + onFilterSelect(isSelected ? null : type)} + sx={{ + ...detailStyles.typeStatsItem, + cursor: 'pointer', + transition: 'all 0.2s', + bgcolor: isSelected ? 'primary.light' : '#fff', + borderColor: isSelected ? 'primary.main' : '#eee', + '&:hover': { + transform: 'translateY(-2px)', + boxShadow: '0 4px 12px rgba(0,0,0,0.1)', + borderColor: 'primary.main' + }, + '& *': { + color: isSelected ? 'primary.contrastText' : undefined + } + }} + > + + {t(QUESTION_TYPE_LABELS[type] || type)} + + + {typeStats.correct} / {typeStats.total} + + + = 60 ? 'success' : 'error'} + /> + + {accuracy.toFixed(0)}% + + + + + ); + })} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/QuestionCard.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/QuestionCard.js new file mode 100644 index 0000000..5d1f9e4 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/components/QuestionCard.js @@ -0,0 +1,362 @@ +'use client'; + +import { useState, useRef, useEffect } from 'react'; +import { Box, Typography, Chip, Paper, Button } from '@mui/material'; +import CheckIcon from '@mui/icons-material/Check'; +import CloseIcon from '@mui/icons-material/Close'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline'; +import ReactMarkdown from 'react-markdown'; +import { detailStyles } from '../detailStyles'; +import { useTranslation } from 'react-i18next'; +import 'github-markdown-css/github-markdown-light.css'; + +// 答题状态常量 +const EVAL_STATUS = { + SUCCESS: 0, + FORMAT_ERROR: 1, + API_ERROR: 2 +}; + +// 状态标签配置 +const STATUS_CONFIG = { + [EVAL_STATUS.SUCCESS]: { label: 'evalTasks.statusSuccess', color: 'success' }, + [EVAL_STATUS.FORMAT_ERROR]: { label: 'evalTasks.statusFormatError', color: 'warning' }, + [EVAL_STATUS.API_ERROR]: { label: 'evalTasks.statusApiError', color: 'error' } +}; + +export default function QuestionCard({ result, index, task }) { + const { t } = useTranslation(); + const { + evalDataset, + modelAnswer, + isCorrect, + score, + judgeResponse, + duration = 0, + status = 0, + errorMessage = '' + } = result; + const { question, questionType, options, correctAnswer } = evalDataset; + + const [isExpanded, setIsExpanded] = useState(false); + const [shouldShowExpand, setShouldShowExpand] = useState(false); + const contentRef = useRef(null); + + const [isCorrectExpanded, setIsCorrectExpanded] = useState(false); + const [shouldShowCorrectExpand, setShouldShowCorrectExpand] = useState(false); + const correctContentRef = useRef(null); + + // 检查内容是否超过高度限制 + useEffect(() => { + if (contentRef.current) { + const hasOverflow = contentRef.current.scrollHeight > 200; + setShouldShowExpand(hasOverflow); + } + }, [modelAnswer]); + + useEffect(() => { + if (correctContentRef.current) { + const hasOverflow = correctContentRef.current.scrollHeight > 200; + setShouldShowCorrectExpand(hasOverflow); + } + }, [correctAnswer]); + + // 解析选项 + let parsedOptions = []; + if (questionType === 'single_choice' || questionType === 'multiple_choice') { + try { + parsedOptions = JSON.parse(options); + } catch (e) { + parsedOptions = options ? [options] : []; + } + } else if (questionType === 'true_false') { + parsedOptions = ['True', 'False']; + } + + // 格式化答案显示 + const formatAnswer = ans => { + if (!ans) return '-'; + return String(ans); + }; + + // 判断选项状态 + const getOptionStatus = (optionText, idx) => { + const letter = String.fromCharCode(65 + idx); + const normModelAns = String(modelAnswer).trim(); + const normCorrectAns = String(correctAnswer).trim(); + + let isSelected = false; + let isCorrectOption = false; + + if (questionType === 'true_false') { + // 判断题:A 对应 ✅/True,B 对应 ❌/False + const isTrueOption = idx === 0; + const isFalseOption = idx === 1; + + isSelected = + (isTrueOption && (normModelAns === '✅' || normModelAns.toUpperCase() === 'TRUE')) || + (isFalseOption && (normModelAns === '❌' || normModelAns.toUpperCase() === 'FALSE')); + + isCorrectOption = + (isTrueOption && (normCorrectAns === '✅' || normCorrectAns.toUpperCase() === 'TRUE')) || + (isFalseOption && (normCorrectAns === '❌' || normCorrectAns.toUpperCase() === 'FALSE')); + } else { + // 选择题逻辑 + const normModelAnsUpper = normModelAns.toUpperCase(); + const normCorrectAnsUpper = normCorrectAns.toUpperCase(); + const normOptionText = String(optionText).toUpperCase(); + + isSelected = normModelAnsUpper.includes(letter) || normModelAnsUpper.includes(normOptionText); + isCorrectOption = normCorrectAnsUpper.includes(letter) || normCorrectAnsUpper.includes(normOptionText); + } + + return { isSelected, isCorrectOption }; + }; + + // 解析 AI 点评内容 + const getJudgeDisplayContent = content => { + if (!content) return ''; + try { + // 尝试从 markdown 代码块中提取 JSON + const jsonMatch = content.match(/\{[\s\S]*?\}/); + if (jsonMatch) { + const parsed = JSON.parse(jsonMatch[0]); + if (parsed.reason) return parsed.reason; + } + // 尝试直接解析 + const parsed = JSON.parse(content); + if (parsed.reason) return parsed.reason; + } catch (e) { + // 解析失败,返回原内容 + } + return content; + }; + + return ( + + {/* 判卷标记 (红勾/红叉) - 绝对定位 */} + + {isCorrect ? : } + + + {/* 题号与类型标签 */} + + + {index + 1} + + + + {/* 答题耗时 */} + {duration > 0 && ( + } + label={duration >= 1000 ? `${(duration / 1000).toFixed(1)}s` : `${duration}ms`} + size="small" + variant="outlined" + sx={{ height: 24, '& .MuiChip-label': { px: 0.75, fontSize: '0.75rem' } }} + /> + )} + + {/* 答题状态 */} + {status !== EVAL_STATUS.SUCCESS && ( + } + label={t( + STATUS_CONFIG[status]?.label || 'evalTasks.statusUnknown', + status === EVAL_STATUS.FORMAT_ERROR ? t('evalTasks.statusFormatError') : t('evalTasks.statusApiError') + )} + size="small" + color={STATUS_CONFIG[status]?.color || 'default'} + variant="outlined" + sx={{ height: 24, '& .MuiChip-label': { px: 0.75, fontSize: '0.75rem' } }} + /> + )} + + + {/* 题目内容 */} + + {question} + + + {/* 选项区域 (仅选择题/判断题) */} + {parsedOptions.length > 0 && ( + + {parsedOptions.map((opt, idx) => { + const letter = String.fromCharCode(65 + idx); + const { isSelected, isCorrectOption } = getOptionStatus(opt, idx); + + return ( + + {letter}. + {opt} + + ); + })} + + )} + + {/* 答案对比区域 */} + + + {t('evalTasks.modelAnswer')} + + + + {questionType === 'open_ended' || questionType === 'short_answer' ? ( +
+ {modelAnswer || ''} +
+ ) : ( + + {formatAnswer(modelAnswer)} + + )} + + {/* 展开/收起 遮罩和按钮 */} + {shouldShowExpand && !isExpanded && ( + + + + )} +
+ + {isExpanded && shouldShowExpand && ( + + + + )} + + + + {t('evalTasks.correctAnswer')} + + + {questionType === 'open_ended' || questionType === 'short_answer' ? ( +
+ {correctAnswer || ''} +
+ ) : ( + + {formatAnswer(correctAnswer)} + + )} + + {/* 展开/收起 遮罩和按钮 */} + {shouldShowCorrectExpand && !isCorrectExpanded && ( + + + + )} +
+ + {isCorrectExpanded && shouldShowCorrectExpand && ( + + + + )} +
+
+ + {/* 错误信息显示 */} + {errorMessage && ( + + + {errorMessage} + + + )} + + {/* 教师点评 (气泡样式) */} + {judgeResponse && ( + + + {t('evalTasks.judgeComment')} + + {getJudgeDisplayContent(judgeResponse)} + + {/* 得分显示(如果是主观题) */} + {(questionType === 'short_answer' || questionType === 'open_ended') && ( + + {(score * 100).toFixed(0)} {t('evalTasks.scoreUnit')} + + )} + + + )} +
+ ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/detailStyles.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/detailStyles.js new file mode 100644 index 0000000..09c3308 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/detailStyles.js @@ -0,0 +1,281 @@ +export const detailStyles = { + // 页面背景 + pageContainer: { + py: 4, + minHeight: '100vh', + bgcolor: '#f5f7fa' + }, + + // 头部概览卡片 + headerCard: { + mb: 3, + borderRadius: 3, + overflow: 'hidden', + boxShadow: '0 4px 20px rgba(0,0,0,0.05)', + border: 'none' + }, + + headerContent: { + p: 3, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + flexWrap: 'wrap', + gap: 2 + }, + + // 分数印章效果 + scoreStamp: (score, isPass) => ({ + width: 110, + height: 110, + borderRadius: '50%', + border: `4px double ${isPass ? '#2e7d32' : '#d32f2f'}`, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + color: isPass ? '#2e7d32' : '#d32f2f', + transform: 'rotate(-15deg)', + maskImage: + 'url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZmlsdGVyIGlkPSJub2lzZSI+PGZlVHVyYnVsZW5jZSB0eXBlPSJmcmFjdGFsTm9pc2UiIGJhc2VGcmVxdWVuY3k9IjAuNSIgbnVtT2N0YXZlcz0iMyIgc3RpdGNoVGlsZXM9InN0aXRjaCIvPjwvZmlsdGVyPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbHRlcj0idXJsKCNub2lzZSkiIG9wYWNpdHk9IjAuNSIvPjwvc3ZnPg==")', // 简单的噪点遮罩模拟印章纹理(可选) + opacity: 0.9, + boxShadow: 'inset 0 0 10px rgba(0,0,0,0.1)', + flexShrink: 0 + }), + + scoreValue: { + fontSize: '2.2rem', + fontWeight: 900, + lineHeight: 1.1, + fontFamily: '"Comic Sans MS", "Chalkboard SE", sans-serif', + mb: 0.2 + }, + + scoreLabel: { + fontSize: '0.75rem', + fontWeight: 700, + textTransform: 'uppercase', + letterSpacing: 1 + }, + + // 统计卡片 + statBox: { + textAlign: 'center', + p: 2, + borderRadius: 2, + bgcolor: 'background.default', + minWidth: 100 + }, + + // 试卷主体 + paperContainer: { + width: '100%', + mx: 'auto', + bgcolor: '#fff', + boxShadow: '0 8px 30px rgba(0,0,0,0.08)', + borderRadius: 2, + overflow: 'hidden', + position: 'relative', + border: '1px solid #e0e0e0' + }, + + paperHeader: { + p: 4, + borderBottom: '2px solid #000', + textAlign: 'center', + position: 'relative', + bgcolor: '#fff' + }, + + paperTitle: { + fontSize: '1.75rem', + fontWeight: 700, + mb: 1, + fontFamily: '"Songti SC", "SimSun", serif' // 宋体增强试卷感 + }, + + paperSubTitle: { + color: 'text.secondary', + fontSize: '0.9rem' + }, + + // 题目部分 + questionSection: { + p: 0 + }, + + questionCard: isCorrect => ({ + p: 3, + height: '100%', // 确保在Grid中高度撑满 + borderBottom: '1px solid #f0f0f0', // 减淡边框颜色 + position: 'relative', + transition: 'all 0.2s ease', + '&:hover': { + bgcolor: '#fafafa' + } + }), + + questionIndex: { + position: 'absolute', + left: 20, + top: 24, + width: 32, + height: 32, + borderRadius: '50%', // 圆形题号 + border: '1px solid #ddd', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + fontWeight: 'bold', + color: 'text.secondary', + bgcolor: '#fff', + zIndex: 1, + fontSize: '0.875rem' + }, + + // 判卷标记(红勾/红叉) + markIcon: isCorrect => ({ + position: 'absolute', + right: 20, + top: 20, + fontSize: '3rem', + color: isCorrect ? '#2e7d32' : '#d32f2f', + opacity: 0.8, + transform: 'rotate(10deg)', + fontFamily: '"Comic Sans MS", "Chalkboard SE", sans-serif' + }), + + // 题目内容 + questionContent: { + fontSize: '1.1rem', + fontWeight: 500, + lineHeight: 1.6, + mb: 2, + color: '#333' + }, + + // 选项区域 + optionsContainer: { + pl: 2, + mb: 2 + }, + + optionItem: (isSelected, isCorrectOption) => ({ + p: 1, + mb: 0.5, + borderRadius: 1, + bgcolor: isCorrectOption + ? 'rgba(46, 125, 50, 0.1)' // 正确选项显示绿色背景 + : isSelected + ? 'rgba(211, 47, 47, 0.1)' + : 'transparent', // 错误选中显示红色背景 + color: isCorrectOption ? 'success.main' : isSelected ? 'error.main' : 'text.primary', + display: 'flex', + alignItems: 'flex-start', + gap: 1 + }), + + // 答案区域 + answerSection: { + mt: 2, + p: 2, + bgcolor: '#f8f9fa', + borderRadius: 2, + borderLeft: '4px solid #ddd', + position: 'relative' + }, + + // Markdown 展示区域 + markdownContainer: isExpanded => ({ + maxHeight: isExpanded ? 'none' : '200px', + overflow: 'hidden', + position: 'relative', + '& .markdown-body': { + fontSize: '0.9rem', + lineHeight: 1.6, + bgcolor: 'transparent', + color: 'inherit', + padding: 0 + } + }), + + // 展开收起遮罩层(渐变效果) + expandMask: { + position: 'absolute', + bottom: 0, + left: 0, + right: 0, + height: '60px', + background: 'linear-gradient(transparent, #f8f9fa)', + display: 'flex', + alignItems: 'flex-end', + justifyContent: 'center', + pb: 1, + zIndex: 1 + }, + + expandButton: { + fontSize: '0.75rem', + textTransform: 'none', + color: 'primary.main', + bgcolor: 'rgba(255,255,255,0.8)', + '&:hover': { + bgcolor: 'rgba(255,255,255,1)' + }, + boxShadow: '0 2px 8px rgba(0,0,0,0.05)', + borderRadius: '16px', + px: 2 + }, + + // 教师点评样式 + judgeComment: { + mt: 2, + position: 'relative', + fontFamily: '"KaiTi", "KaiTi_GB2312", serif', // 楷体模拟手写点评 + color: '#d32f2f', + padding: '10px 20px', + border: '1px solid #d32f2f', + borderRadius: '20px 20px 20px 4px', // 气泡形状 + maxWidth: 'fit-content', + bgcolor: '#fff5f5' + }, + + judgeLabel: { + fontSize: '0.8rem', + opacity: 0.7, + fontStyle: 'italic', + mb: 0.5 + }, + + // 按题型统计样式 + typeStatsItem: { + textAlign: 'center', + p: 2, + bgcolor: '#fff', + borderRadius: 2, + border: '1px solid #eee', + boxShadow: '0 2px 8px rgba(0,0,0,0.03)', + height: '100%', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center' + }, + + typeStatsLabel: { + fontSize: '0.85rem', + color: 'text.secondary', + mb: 1 + }, + + typeStatsScore: { + fontWeight: 700, + fontSize: '1.25rem', + color: 'text.primary' + }, + + typeStatsPercent: { + fontSize: '0.75rem', + color: 'text.secondary', + fontWeight: 500 + } +}; diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/page.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/page.js new file mode 100644 index 0000000..b9550db --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/[taskId]/page.js @@ -0,0 +1,214 @@ +'use client'; + +import { useParams, useRouter } from 'next/navigation'; +import { + Container, + Box, + Button, + CircularProgress, + Alert, + Typography, + LinearProgress, + Paper, + Grid, + Pagination +} from '@mui/material'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import { useTranslation } from 'react-i18next'; +import useEvalTaskDetail from '../hooks/useEvalTaskDetail'; +import { detailStyles } from './detailStyles'; +import EvalHeader from './components/EvalHeader'; +import EvalStats from './components/EvalStats'; +import QuestionCard from './components/QuestionCard'; + +export default function EvalTaskDetailPage() { + const { projectId, taskId } = useParams(); + const router = useRouter(); + const { t } = useTranslation(); + + const { + task, + results, + stats, + total, + page, + setPage, + pageSize, + filterType, + setFilterType, + filterCorrect, + setFilterCorrect, + loading, + error, + setError, + loadData + } = useEvalTaskDetail(projectId, taskId); + + const handleFilterSelect = type => { + setFilterType(type); + setPage(1); // 切换筛选时重置到第一页 + }; + + const handleFilterCorrectSelect = isCorrect => { + setFilterCorrect(isCorrect); + setPage(1); // 切换筛选时重置到第一页 + }; + + const handlePageChange = (event, value) => { + setPage(value); + // 滚动到试卷顶部 + document.getElementById('paper-top')?.scrollIntoView({ behavior: 'smooth' }); + }; + + if (loading && !task) { + return ( + + + + ); + } + + return ( + + + {/* 顶部导航栏 */} + + + + + + {/* 错误提示 */} + {error && ( + setError('')}> + {error} + + )} + + {/* 任务进度(仅进行中时显示) */} + {task?.status === 0 && ( + + + + {t('evalTasks.statusProcessing')}... + + + {task.completedCount}/{task.totalCount} + + + 0 ? (task.completedCount / task.totalCount) * 100 : 0} + sx={{ height: 10, borderRadius: 5 }} + /> + + )} + + {/* 核心内容区 */} + {task && ( + <> + {/* 头部概览 */} + + + {/* 统计图表 & 筛选 */} + + + {/* 试卷主体 */} + + {/* 试卷抬头 */} + + {t('evalTasks.reportTitle', '模型能力评估报告')} + + + {t('evalTasks.taskIdLabel', '任务 ID')}: {taskId} + + + {t('evalTasks.pageInfo', '第 {{page}} / {{totalPages}} 页', { + page, + totalPages: Math.ceil(total / pageSize) + })} + + + + + {/* 题目列表 (双列布局) */} + + {loading ? ( + + + + ) : ( + + {results?.map((result, index) => ( + + + + ))} + + )} + + {!loading && results?.length === 0 && ( + + {t('evalTasks.noMatchingResults', '暂无符合条件的评估结果')} + + )} + + + {/* 分页控制 */} + + + + + {/* 试卷底部 */} + + + {t('evalTasks.reportFooter', 'Easy Dataset Evaluation System · Generated by AI')} + + + + + )} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/CreateEvalTaskDialog.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/CreateEvalTaskDialog.js new file mode 100644 index 0000000..5dc90b7 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/CreateEvalTaskDialog.js @@ -0,0 +1,328 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + FormControl, + InputLabel, + Select, + MenuItem, + Alert, + Divider, + CircularProgress, + FormHelperText +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import ModelSelector from './ModelSelector'; +import QuestionFilter from './QuestionFilter'; +import ScoreAnchorsForm from './ScoreAnchorsForm'; +import { useEvalTaskForm } from '../hooks/useEvalTaskForm'; + +import { useEffect } from 'react'; + +export default function CreateEvalTaskDialog({ open, onClose, projectId, onSuccess }) { + const { t, i18n } = useTranslation(); + const [submitting, setSubmitting] = useState(false); + + const { + models, + selectedModels, + setSelectedModels, + judgeModel, + setJudgeModel, + evalDatasets, + availableTags, + questionTypes, + setQuestionTypes, + selectedTags, + setSelectedTags, + searchKeyword, + setSearchKeyword, + questionCount, + setQuestionCount, + filteredTotal, + sampledIds, + hasSubjectiveQuestions, + hasShortAnswer, + hasOpenEnded, + shortAnswerScoreAnchors, + setShortAnswerScoreAnchors, + openEndedScoreAnchors, + setOpenEndedScoreAnchors, + initScoreAnchors, + loading, + error, + setError, + setSampledIds, + resetFilters, + resetForm + } = useEvalTaskForm(projectId, open); + + // 当有主观题时,初始化评分规则 + useEffect(() => { + if (hasSubjectiveQuestions && open) { + initScoreAnchors(i18n.language === 'zh-CN' ? 'zh-CN' : 'en'); + } + }, [hasSubjectiveQuestions, open, i18n.language]); + + // 统计各题型数量 + const typeStats = {}; + evalDatasets.forEach(d => { + typeStats[d.questionType] = (typeStats[d.questionType] || 0) + 1; + }); + + const getModelKey = model => `${model.providerId}::${model.modelId}`; + + const handleModelSelectionChange = newSelection => { + setSelectedModels(newSelection); + setError(''); + }; + + const handleSubmit = async () => { + // 先清除之前的错误 + setError(''); + + // 验证 + if (selectedModels.length === 0) { + setError(t('evalTasks.errorNoModels')); + return; + } + + if (filteredTotal === 0) { + setError(t('evalTasks.errorNoQuestions')); + return; + } + + if (hasSubjectiveQuestions && !judgeModel) { + setError(t('evalTasks.errorNoJudgeModel')); + return; + } + + // 验证教师模型不在测试模型中 + if (judgeModel && selectedModels.includes(judgeModel)) { + setError(t('evalTasks.errorJudgeSameAsTest')); + return; + } + + try { + setSubmitting(true); + setError(''); + + // 解析选中的模型 + const models = selectedModels.map(m => { + const [providerId, modelId] = m.split('::'); + return { modelId, providerId }; // 注意顺序:modelId 在前 + }); + + // 解析教师模型 + let judgeModelId = null; + let judgeProviderId = null; + if (judgeModel) { + const [pId, mId] = judgeModel.split('::'); + judgeProviderId = pId; + judgeModelId = mId; + } + + // 调用后端采样接口获取题目 ID + const sampleBody = { + questionTypes: questionTypes, + tags: selectedTags, + keyword: searchKeyword.trim() || '', + limit: questionCount > 0 ? questionCount : undefined + }; + + const sampleResponse = await fetch(`/api/projects/${projectId}/eval-datasets/sample`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(sampleBody) + }); + + const sampleResult = await sampleResponse.json(); + if (!sampleResponse.ok || sampleResult.code !== 0) { + setError(sampleResult.error || t('evalTasks.errorCreateFailed')); + return; + } + + const ids = sampleResult?.data?.ids || []; + if (ids.length === 0) { + setError(t('evalTasks.errorNoQuestions')); + return; + } + + setSampledIds(ids); + + // 构建自定义评分规则对象 + const customScoreAnchors = {}; + if (hasShortAnswer && shortAnswerScoreAnchors.length > 0) { + customScoreAnchors.short_answer = shortAnswerScoreAnchors; + } + if (hasOpenEnded && openEndedScoreAnchors.length > 0) { + customScoreAnchors.open_ended = openEndedScoreAnchors; + } + + // 创建任务 + const response = await fetch(`/api/projects/${projectId}/eval-tasks`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + models, // 后端期望的字段名 + judgeModelId, // 分开传递 + judgeProviderId, // 分开传递 + evalDatasetIds: ids, + language: i18n.language === 'zh-CN' ? 'zh-CN' : 'en', + customScoreAnchors: Object.keys(customScoreAnchors).length > 0 ? customScoreAnchors : undefined + }) + }); + + const result = await response.json(); + + if (result.code === 0) { + onSuccess && onSuccess(result.data); + handleClose(); + } else { + setError(result.error || t('evalTasks.errorCreateFailed')); + } + } catch (err) { + console.error('创建评估任务失败:', err); + setError(t('evalTasks.errorCreateFailed')); + } finally { + setSubmitting(false); + } + }; + + const handleClose = () => { + resetForm(); + onClose(); + }; + + const handleJudgeModelChange = event => { + setJudgeModel(event.target.value); + setError(''); + }; + + return ( + + {t('evalTasks.createTitle')} + + + {error && ( + setError('')}> + {error} + + )} + + {/* 选择测试模型 */} + + + + + {/* 题目筛选 */} + + + {/* 最终题目统计 */} + + + {t('evalTasks.finalSelection')} + {sampledIds.length || (questionCount > 0 ? questionCount : filteredTotal)}{' '} + {t('evalTasks.questionsSuffix')} + + {hasSubjectiveQuestions && ( + + {t('evalTasks.hasSubjectiveHint')} + + )} + + + {/* 选择教师模型(仅当有主观题时显示) */} + {hasSubjectiveQuestions && ( + <> + + {t('evalTasks.selectJudgeModel')} * + + {t('evalTasks.selectJudgeModelHint')} + + + {/* 简答题评分规则 */} + {hasShortAnswer && ( + + )} + + {/* 开放题评分规则 */} + {hasOpenEnded && ( + + )} + + )} + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/EvalTaskCard.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/EvalTaskCard.js new file mode 100644 index 0000000..f395bc9 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/EvalTaskCard.js @@ -0,0 +1,183 @@ +'use client'; + +import { useState } from 'react'; +import { + Card, + CardContent, + Box, + Typography, + Chip, + IconButton, + LinearProgress, + Menu, + MenuItem, + ListItemIcon, + Avatar, + useTheme +} from '@mui/material'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import DeleteIcon from '@mui/icons-material/Delete'; +import StopIcon from '@mui/icons-material/Stop'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import ErrorIcon from '@mui/icons-material/Error'; +import PauseCircleIcon from '@mui/icons-material/PauseCircle'; +import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import QuizIcon from '@mui/icons-material/Quiz'; +import { useTranslation } from 'react-i18next'; +import { getModelIcon } from '@/lib/util/modelIcon'; +import styles from '../styles'; + +const STATUS_CONFIG = { + 0: { label: 'evalTasks.statusProcessing', color: 'info', icon: HourglassEmptyIcon }, + 1: { label: 'evalTasks.statusCompleted', color: 'success', icon: CheckCircleIcon }, + 2: { label: 'evalTasks.statusFailed', color: 'error', icon: ErrorIcon }, + 3: { label: 'evalTasks.statusInterrupted', color: 'warning', icon: PauseCircleIcon } +}; + +export default function EvalTaskCard({ task, onView, onDelete, onInterrupt }) { + const { t } = useTranslation(); + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + const { modelInfo, detail, status, completedCount, totalCount, createAt } = task; + const statusConfig = STATUS_CONFIG[status] || STATUS_CONFIG[0]; + const StatusIcon = statusConfig.icon; + const progress = totalCount > 0 ? (completedCount / totalCount) * 100 : 0; + const finalScore = detail?.finalScore; + + const handleMenuClick = e => { + e.stopPropagation(); + setAnchorEl(e.currentTarget); + }; + + const handleMenuClose = () => setAnchorEl(null); + + const handleAction = action => () => { + handleMenuClose(); + action?.(task); + }; + + const getScoreColor = score => { + if (score >= 80) return 'success'; + if (score >= 60) return 'info'; + if (score >= 40) return 'warning'; + return 'error'; + }; + + return ( + + + {/* 头部 */} + + + + {modelInfo?.modelId + + + + {modelInfo?.modelName || modelInfo?.modelId} + + + {modelInfo?.providerName || modelInfo?.providerId} + + + + + + + + + {/* 状态和得分 */} + + } + label={t(statusConfig.label)} + color={statusConfig.color} + size="small" + variant="outlined" + sx={{ height: 24, '& .MuiChip-label': { px: 1, fontSize: '0.7rem' } }} + /> + {finalScore !== undefined && status === 1 && ( + + )} + + + {/* 进度条 */} + {status === 0 && ( + + + + {t('evalTasks.progress')} + + + {completedCount}/{totalCount} + + + + + )} + + {/* 统计信息 */} + + } + label={`${totalCount} ${t('evalTasks.questions')}`} + size="small" + variant="outlined" + sx={{ height: 22, '& .MuiChip-label': { px: 0.75, fontSize: '0.7rem' } }} + /> + {detail?.hasSubjectiveQuestions && ( + + )} + + + {/* 时间 */} + + {new Date(createAt).toLocaleString()} + + + + {/* 菜单 */} + e.stopPropagation()}> + + + + + {t('datasets.viewDetails')} + + {status === 0 && ( + + + + + {t('evalTasks.interrupt')} + + )} + + + + + {t('common.delete')} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/ModelSelector.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/ModelSelector.js new file mode 100644 index 0000000..cc95d05 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/ModelSelector.js @@ -0,0 +1,87 @@ +'use client'; + +import { + Box, + Typography, + Checkbox, + FormHelperText, + FormControl, + InputLabel, + Select, + MenuItem, + ListItemText, + OutlinedInput, + Chip +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export default function ModelSelector({ models, selectedModels, onSelectionChange, error }) { + const { t } = useTranslation(); + + const getModelKey = model => `${model.providerId}::${model.modelId}`; + + const handleChange = event => { + const { + target: { value } + } = event; + // On autofill we get a stringified value. + onSelectionChange(typeof value === 'string' ? value.split(',') : value); + }; + + const getModelLabel = modelKey => { + const model = models.find(m => getModelKey(m) === modelKey); + if (!model) return modelKey; + return `${model.providerName || model.providerId} / ${model.modelName || model.modelId}`; + }; + + return ( + + + {t('evalTasks.selectModels')} * + + {error || t('evalTasks.selectModelsHint')} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/QuestionFilter.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/QuestionFilter.js new file mode 100644 index 0000000..02322bd --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/QuestionFilter.js @@ -0,0 +1,160 @@ +'use client'; + +import { + Box, + Typography, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Chip, + OutlinedInput, + Checkbox, + ListItemText, + Slider, + Button +} from '@mui/material'; +import FilterAltIcon from '@mui/icons-material/FilterAlt'; +import ClearIcon from '@mui/icons-material/Clear'; +import { useTranslation } from 'react-i18next'; + +const QUESTION_TYPES = [ + { value: 'true_false', labelKey: 'eval.questionTypes.true_false' }, + { value: 'single_choice', labelKey: 'eval.questionTypes.single_choice' }, + { value: 'multiple_choice', labelKey: 'eval.questionTypes.multiple_choice' }, + { value: 'short_answer', labelKey: 'eval.questionTypes.short_answer' }, + { value: 'open_ended', labelKey: 'eval.questionTypes.open_ended' } +]; + +export default function QuestionFilter({ + questionTypes, + selectedTags, + searchKeyword, + questionCount, + availableTags, + filteredCount, + onQuestionTypesChange, + onTagsChange, + onSearchChange, + onQuestionCountChange, + onReset +}) { + const { t } = useTranslation(); + + const hasFilters = questionTypes.length > 0 || selectedTags.length > 0 || searchKeyword || questionCount > 0; + + return ( + + + + + {t('evalTasks.filterTitle')} + + {hasFilters && ( + + )} + + + + {/* 关键字搜索 */} + onSearchChange(e.target.value)} + /> + + {/* 题型和标签筛选 - 并排显示 */} + + {/* 题型筛选 */} + + {t('evalTasks.filterByTypeLabel')} + + + + {/* 标签筛选 */} + {availableTags.length > 0 && ( + + {t('evalTasks.filterByTagLabel')} + + + )} + + + {/* 题目数量选择 - 紧凑布局 */} + + + + {t('evalTasks.questionCountLabel')} + {questionCount === 0 ? t('common.all') : questionCount} / {filteredCount} + + onQuestionCountChange(parseInt(e.target.value) || 0)} + inputProps={{ min: 0, max: filteredCount }} + sx={{ width: 100 }} + /> + + onQuestionCountChange(value)} + min={0} + max={filteredCount} + step={1} + valueLabelDisplay="auto" + /> + + {questionCount === 0 + ? t('evalTasks.useAllQuestions') + : t('evalTasks.randomSampleHint', { filteredCount, questionCount })} + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/ScoreAnchorsForm.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/ScoreAnchorsForm.js new file mode 100644 index 0000000..eb26503 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/components/ScoreAnchorsForm.js @@ -0,0 +1,143 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Box, + Typography, + TextField, + Accordion, + AccordionSummary, + AccordionDetails, + Chip, + IconButton, + Tooltip +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import RestoreIcon from '@mui/icons-material/Restore'; +import { useTranslation } from 'react-i18next'; +import { getDefaultScoreAnchors } from '@/lib/llm/prompts/llmJudge'; + +/** + * 评分规则表单组件 + * 用于自定义简答题和开放题的评分规则 + */ +export default function ScoreAnchorsForm({ + questionType, // 'short_answer' 或 'open_ended' + scoreAnchors, + onChange, + language = 'zh-CN' +}) { + const { t, i18n } = useTranslation(); + const [expanded, setExpanded] = useState(false); + + // 获取当前语言 + const currentLanguage = i18n.language === 'zh-CN' ? 'zh-CN' : 'en'; + + // 初始化评分规则(如果为空) + useEffect(() => { + if (!scoreAnchors || scoreAnchors.length === 0) { + onChange(getDefaultScoreAnchors(questionType, currentLanguage)); + } + }, [questionType, currentLanguage]); + + // 处理单个规则的描述更改 + const handleDescriptionChange = (index, newDescription) => { + const newAnchors = [...scoreAnchors]; + newAnchors[index] = { ...newAnchors[index], description: newDescription }; + onChange(newAnchors); + }; + + // 恢复默认值 + const handleRestore = () => { + onChange(getDefaultScoreAnchors(questionType, currentLanguage)); + }; + + // 获取题型显示名称 + const getQuestionTypeName = () => { + if (questionType === 'short_answer') { + return t('evalTasks.shortAnswer', '简答题'); + } + return t('evalTasks.openEnded', '开放题'); + }; + + // 获取分数区间的颜色 + const getScoreColor = range => { + if (range === '1.0') return 'success'; + if (range.includes('0.8') || range.includes('0.9')) return 'info'; + if (range.includes('0.6') || range.includes('0.7')) return 'warning'; + return 'error'; + }; + + if (!scoreAnchors || scoreAnchors.length === 0) { + return null; + } + + return ( + setExpanded(isExpanded)} + sx={{ + mb: 2, + '&:before': { display: 'none' }, + boxShadow: 1 + }} + > + } + sx={{ + bgcolor: 'action.hover', + '&:hover': { bgcolor: 'action.selected' } + }} + > + + + {t('evalTasks.scoreAnchorsTitle', '{{type}}评分规则', { type: getQuestionTypeName() })} + + + + + + + + {t('evalTasks.scoreAnchorsHint', '自定义评分标准,用于指导LLM评估模型的回答质量')} + + + + + + + + + {scoreAnchors.map((anchor, index) => ( + + + + + {t('evalTasks.scoreRange', '分数区间')} + + + handleDescriptionChange(index, e.target.value)} + placeholder={t('evalTasks.scoreDescriptionPlaceholder', '请输入该分数区间的评分标准描述...')} + sx={{ + '& .MuiOutlinedInput-root': { + fontSize: '0.875rem' + } + }} + /> + + ))} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTaskDetail.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTaskDetail.js new file mode 100644 index 0000000..4854aeb --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTaskDetail.js @@ -0,0 +1,94 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; + +/** + * 评估任务详情 Hook + */ +export default function useEvalTaskDetail(projectId, taskId) { + const [task, setTask] = useState(null); + const [results, setResults] = useState([]); + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + + // 分页和筛选状态 + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [filterType, setFilterType] = useState(null); + const [filterCorrect, setFilterCorrect] = useState(null); // null: all, true: correct, false: incorrect + const [total, setTotal] = useState(0); + + // 加载任务详情 + const loadData = useCallback(async () => { + if (!projectId || !taskId) return; + + try { + setLoading(true); + setError(''); + + // 构建查询参数 + const params = new URLSearchParams({ + page: page.toString(), + pageSize: pageSize.toString() + }); + + if (filterType) { + params.append('type', filterType); + } + + if (filterCorrect !== null) { + params.append('isCorrect', filterCorrect.toString()); + } + + const response = await fetch(`/api/projects/${projectId}/eval-tasks/${taskId}?${params.toString()}`); + const result = await response.json(); + + if (result.code === 0) { + setTask(result.data.task); + setResults(result.data.results || []); + setTotal(result.data.total || 0); + setStats(result.data.stats); + } else { + setError(result.error || '加载失败'); + } + } catch (err) { + console.error('加载任务详情失败:', err); + setError('加载失败'); + } finally { + setLoading(false); + } + }, [projectId, taskId, page, pageSize, filterType, filterCorrect]); + + // 初始加载 + useEffect(() => { + loadData(); + }, [loadData]); + + // 自动刷新进行中的任务 (仅在第一页且无筛选时刷新,避免干扰用户查看历史记录) + useEffect(() => { + if (task?.status !== 0 || page !== 1 || filterType || filterCorrect !== null) return; + + const interval = setInterval(loadData, 3000); + return () => clearInterval(interval); + }, [task?.status, page, filterType, filterCorrect, loadData]); + + return { + task, + results, + stats, + total, + page, + setPage, + pageSize, + setPageSize, + filterType, + setFilterType, + filterCorrect, + setFilterCorrect, + loading, + error, + setError, + loadData + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTaskForm.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTaskForm.js new file mode 100644 index 0000000..1257b18 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTaskForm.js @@ -0,0 +1,187 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { getDefaultScoreAnchors } from '@/lib/llm/prompts/llmJudge'; + +export function useEvalTaskForm(projectId, open) { + const [models, setModels] = useState([]); + const [selectedModels, setSelectedModels] = useState([]); + const [judgeModel, setJudgeModel] = useState(''); + const [evalDatasets, setEvalDatasets] = useState([]); + const [availableTags, setAvailableTags] = useState([]); + + // 筛选条件 + const [questionTypes, setQuestionTypes] = useState([]); + const [selectedTags, setSelectedTags] = useState([]); + const [searchKeyword, setSearchKeyword] = useState(''); + const [questionCount, setQuestionCount] = useState(0); + + // 后端统计 & 采样结果 + const [filteredTotal, setFilteredTotal] = useState(0); + const [sampledIds, setSampledIds] = useState([]); + const [hasSubjectiveQuestions, setHasSubjectiveQuestions] = useState(false); + // 主观题类型统计(用于确定显示哪个评分规则表单) + const [hasShortAnswer, setHasShortAnswer] = useState(false); + const [hasOpenEnded, setHasOpenEnded] = useState(false); + + // 自定义评分规则 + const [shortAnswerScoreAnchors, setShortAnswerScoreAnchors] = useState([]); + const [openEndedScoreAnchors, setOpenEndedScoreAnchors] = useState([]); + + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + // 加载数据 + useEffect(() => { + if (open && projectId) { + loadModels(); + loadEvalDatasets(); + } + }, [open, projectId]); + + // 当筛选条件变化时,调用后端统计数量 + useEffect(() => { + if (!open || !projectId) return; + + const controller = new AbortController(); + + const fetchCount = async () => { + try { + const params = new URLSearchParams(); + if (questionTypes.length > 0) { + questionTypes.forEach(t => params.append('questionTypes', t)); + } + if (searchKeyword.trim()) { + params.append('keyword', searchKeyword.trim()); + } + if (selectedTags.length > 0) { + selectedTags.forEach(tag => params.append('tags', tag)); + } + + const response = await fetch(`/api/projects/${projectId}/eval-datasets/count?${params.toString()}`, { + signal: controller.signal + }); + if (response.ok) { + const result = await response.json(); + const total = result?.data?.total ?? 0; + const hasSubjective = result?.data?.hasSubjective ?? false; + const hasShort = result?.data?.hasShortAnswer ?? false; + const hasOpen = result?.data?.hasOpenEnded ?? false; + setFilteredTotal(total); + setHasSubjectiveQuestions(hasSubjective); + setHasShortAnswer(hasShort); + setHasOpenEnded(hasOpen); + } + } catch (err) { + if (err.name !== 'AbortError') { + console.error('加载评估题目数量失败:', err); + } + } + }; + + fetchCount(); + + return () => { + controller.abort(); + }; + }, [open, projectId, questionTypes, selectedTags, searchKeyword]); + + const loadModels = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/model-config`); + if (response.ok) { + const result = await response.json(); + const modelList = result?.data || []; + const availableModels = modelList.filter(m => m.apiKey && m.apiKey.trim() !== '' && m.status === 1); + setModels(availableModels); + } + } catch (err) { + console.error('加载模型列表失败:', err); + setModels([]); + } + }; + + const loadEvalDatasets = async () => { + try { + setLoading(true); + // 这里只需要拿到全部可用标签和题型分布,可以复用已有列表接口或标签接口 + const response = await fetch(`/api/projects/${projectId}/eval-datasets?includeStats=true&page=1&pageSize=20`); + if (response.ok) { + const data = await response.json(); + const stats = data.stats || {}; + const byTag = stats.byTag || {}; + const tags = Object.keys(byTag); + setAvailableTags(tags.sort()); + + // 用部分数据来判断是否存在主观题(类型统计更准确) + const byType = stats.byType || {}; + const mockDatasets = Object.entries(byType).map(([type]) => ({ questionType: type })); + setEvalDatasets(mockDatasets); + } + } catch (err) { + console.error('加载评估题目失败:', err); + } finally { + setLoading(false); + } + }; + + const resetFilters = () => { + setQuestionTypes([]); + setSelectedTags([]); + setSearchKeyword(''); + setQuestionCount(0); + setFilteredTotal(0); + setSampledIds([]); + setHasShortAnswer(false); + setHasOpenEnded(false); + }; + + // 初始化评分规则(根据语言环境) + const initScoreAnchors = (language = 'zh-CN') => { + setShortAnswerScoreAnchors(getDefaultScoreAnchors('short_answer', language)); + setOpenEndedScoreAnchors(getDefaultScoreAnchors('open_ended', language)); + }; + + const resetForm = () => { + setSelectedModels([]); + setJudgeModel(''); + resetFilters(); + setError(''); + setShortAnswerScoreAnchors([]); + setOpenEndedScoreAnchors([]); + }; + + return { + models, + selectedModels, + setSelectedModels, + judgeModel, + setJudgeModel, + evalDatasets, + availableTags, + questionTypes, + setQuestionTypes, + selectedTags, + setSelectedTags, + searchKeyword, + setSearchKeyword, + questionCount, + setQuestionCount, + filteredTotal, + sampledIds, + hasSubjectiveQuestions, + hasShortAnswer, + hasOpenEnded, + shortAnswerScoreAnchors, + setShortAnswerScoreAnchors, + openEndedScoreAnchors, + setOpenEndedScoreAnchors, + initScoreAnchors, + loading, + error, + setError, + setSampledIds, + resetFilters, + resetForm + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTasks.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTasks.js new file mode 100644 index 0000000..89f359f --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/hooks/useEvalTasks.js @@ -0,0 +1,149 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; + +/** + * 评估任务列表 Hook + */ +export default function useEvalTasks(projectId) { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(12); + const [total, setTotal] = useState(0); + + // 加载任务列表 + const loadTasks = useCallback( + async (isRefresh = false) => { + if (!projectId) return; + + try { + if (!isRefresh) setLoading(true); + setError(''); + const response = await fetch(`/api/projects/${projectId}/eval-tasks?page=${page}&pageSize=${pageSize}`); + const result = await response.json(); + + if (result.code === 0) { + setTasks(result.data.items || []); + setTotal(result.data.total || 0); + } else { + setError(result.error || '加载失败'); + } + } catch (err) { + console.error('加载评估任务失败:', err); + setError('加载失败'); + } finally { + if (!isRefresh) setLoading(false); + } + }, + [projectId, page, pageSize] + ); + + // 初始加载和分页变化加载 + useEffect(() => { + loadTasks(); + }, [loadTasks]); + + // 自动刷新进行中的任务 + useEffect(() => { + const hasProcessingTasks = tasks.some(t => t.status === 0); + if (!hasProcessingTasks) return; + + const interval = setInterval(() => loadTasks(true), 5000); + return () => clearInterval(interval); + }, [tasks, loadTasks]); + + // 删除任务 + const deleteTask = useCallback( + async taskId => { + try { + const response = await fetch(`/api/projects/${projectId}/eval-tasks/${taskId}`, { + method: 'DELETE' + }); + const result = await response.json(); + + if (result.code === 0) { + loadTasks(); + return true; + } else { + setError(result.error || '删除失败'); + return false; + } + } catch (err) { + console.error('删除任务失败:', err); + setError('删除失败'); + return false; + } + }, + [projectId] + ); + + // 中断任务 + const interruptTask = useCallback( + async taskId => { + try { + const response = await fetch(`/api/projects/${projectId}/eval-tasks/${taskId}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ action: 'interrupt' }) + }); + const result = await response.json(); + + if (result.code === 0) { + loadTasks(); + return true; + } else { + setError(result.error || '中断失败'); + return false; + } + } catch (err) { + console.error('中断任务失败:', err); + setError('中断失败'); + return false; + } + }, + [projectId, loadTasks] + ); + + // 创建任务 + const createTasks = useCallback( + async data => { + try { + const response = await fetch(`/api/projects/${projectId}/eval-tasks`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(data) + }); + const result = await response.json(); + + if (result.code === 0) { + loadTasks(); + return { success: true, data: result.data }; + } else { + return { success: false, error: result.error }; + } + } catch (err) { + console.error('创建任务失败:', err); + return { success: false, error: '创建失败' }; + } + }, + [projectId, loadTasks] + ); + + return { + tasks, + loading, + error, + setError, + loadTasks, + deleteTask, + interruptTask, + createTasks, + page, + setPage, + pageSize, + setPageSize, + total + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/page.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/page.js new file mode 100644 index 0000000..9f0ffb8 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/page.js @@ -0,0 +1,188 @@ +'use client'; + +import { useState } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { + Container, + Typography, + Box, + Paper, + Button, + Grid, + CircularProgress, + Alert, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + TablePagination +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import { useTranslation } from 'react-i18next'; + +import useEvalTasks from './hooks/useEvalTasks'; +import CreateEvalTaskDialog from './components/CreateEvalTaskDialog'; +import EvalTaskCard from './components/EvalTaskCard'; +import styles from './styles'; + +export default function EvalTasksPage() { + const { projectId } = useParams(); + const router = useRouter(); + const { t } = useTranslation(); + + const { + tasks, + loading, + error, + setError, + loadTasks, + deleteTask, + interruptTask, + page, + setPage, + pageSize, + setPageSize, + total + } = useEvalTasks(projectId); + + const [createDialogOpen, setCreateDialogOpen] = useState(false); + const [deleteDialog, setDeleteDialog] = useState({ open: false, task: null }); + const [interruptDialog, setInterruptDialog] = useState({ open: false, task: null }); + + const handleView = task => router.push(`/projects/${projectId}/eval-tasks/${task.id}`); + const handleDelete = task => setDeleteDialog({ open: true, task }); + const handleInterrupt = task => setInterruptDialog({ open: true, task }); + + const handlePageChange = (event, newPage) => { + setPage(newPage + 1); + }; + + const handlePageSizeChange = event => { + setPageSize(parseInt(event.target.value, 10)); + setPage(1); + }; + + const confirmDelete = async () => { + if (deleteDialog.task) { + await deleteTask(deleteDialog.task.id); + } + setDeleteDialog({ open: false, task: null }); + }; + + const confirmInterrupt = async () => { + if (interruptDialog.task) { + await interruptTask(interruptDialog.task.id); + } + setInterruptDialog({ open: false, task: null }); + }; + + return ( + + {/* 标题栏 */} + + + {t('evalTasks.title')} + + + + + + + {/* 错误提示 */} + {error && ( + setError('')}> + {error} + + )} + + {/* 加载状态 */} + {loading && tasks.length === 0 && ( + + + + )} + + {/* 空状态 */} + {!loading && tasks.length === 0 && ( + + + + {t('evalTasks.noTasks')} + + + {t('evalTasks.noTasksHint')} + + + + )} + + {/* 任务列表 */} + {tasks.length > 0 && ( + <> + + {tasks.map(task => ( + + + + ))} + + + + + + )} + + {/* 创建任务对话框 */} + setCreateDialogOpen(false)} + projectId={projectId} + onSuccess={loadTasks} + /> + + {/* 删除确认对话框 */} + setDeleteDialog({ open: false, task: null })}> + {t('evalTasks.deleteConfirmTitle')} + + {t('evalTasks.deleteConfirmMessage')} + + + + + + + + {/* 中断确认对话框 */} + setInterruptDialog({ open: false, task: null })}> + {t('evalTasks.interruptConfirmTitle')} + + {t('evalTasks.interruptConfirmMessage')} + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/eval-tasks/styles.js b/easy-dataset-main/app/projects/[projectId]/eval-tasks/styles.js new file mode 100644 index 0000000..a37380b --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/eval-tasks/styles.js @@ -0,0 +1,280 @@ +/** + * 评估任务页面样式 + */ + +export const evalTasksStyles = { + // 页面容器 + pageContainer: { + py: 3, + minHeight: '100vh' + }, + + // 页头 + header: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + mb: 3 + }, + + headerTitle: { + fontWeight: 600 + }, + + headerActions: { + display: 'flex', + gap: 1 + }, + + // 空状态 + emptyState: { + p: 8, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + minHeight: 400, + borderRadius: 3, + bgcolor: 'background.paper' + }, + + emptyIcon: { + fontSize: 80, + color: 'text.disabled', + mb: 2 + }, + + emptyTitle: { + mb: 1, + fontWeight: 500 + }, + + emptyHint: { + mb: 4, + textAlign: 'center', + maxWidth: 400 + }, + + // 任务卡片 + taskCard: theme => ({ + height: '100%', + cursor: 'pointer', + transition: 'all 0.2s ease', + borderRadius: 2, + overflow: 'hidden', + border: `1px solid ${theme.palette.divider}`, + '&:hover': { + boxShadow: theme.shadows[6], + transform: 'translateY(-4px)', + borderColor: theme.palette.primary.main + } + }), + + taskCardContent: { + p: 2.5 + }, + + taskCardHeader: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'flex-start', + mb: 2 + }, + + taskCardModel: { + flex: 1, + overflow: 'hidden' + }, + + taskCardModelName: { + fontWeight: 600, + fontSize: '0.95rem', + lineHeight: 1.3 + }, + + taskCardTime: { + mt: 0.5, + fontSize: '0.75rem' + }, + + taskCardStatus: { + display: 'flex', + alignItems: 'center', + gap: 1, + mb: 2 + }, + + taskCardProgress: { + mb: 2 + }, + + progressBar: { + height: 6, + borderRadius: 3 + }, + + taskCardStats: { + display: 'flex', + gap: 1, + flexWrap: 'wrap' + }, + + // 统计卡片 + statsCard: theme => ({ + height: '100%', + borderRadius: 2, + border: `1px solid ${theme.palette.divider}`, + transition: 'all 0.2s ease', + '&:hover': { + boxShadow: theme.shadows[2] + } + }), + + statsCardContent: { + p: 2.5 + }, + + statsLabel: { + fontSize: '0.75rem', + color: 'text.secondary', + mb: 1, + textTransform: 'uppercase', + letterSpacing: 0.5 + }, + + statsValue: { + fontWeight: 700, + fontSize: '1.75rem', + lineHeight: 1.2 + }, + + // 按题型统计 + typeStatsContainer: { + p: 2.5, + mb: 3, + borderRadius: 2 + }, + + typeStatsTitle: { + fontWeight: 600, + mb: 2 + }, + + typeStatsItem: theme => ({ + textAlign: 'center', + p: 1.5, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.02)', + borderRadius: 1.5, + border: `1px solid ${theme.palette.divider}` + }), + + typeStatsLabel: { + fontSize: '0.7rem', + color: 'text.secondary', + mb: 0.5 + }, + + typeStatsScore: { + fontWeight: 700, + fontSize: '1.1rem' + }, + + typeStatsPercent: { + fontSize: '0.7rem', + color: 'text.secondary' + }, + + // 结果表格 + resultsTable: { + overflow: 'hidden', + borderRadius: 2 + }, + + resultsTableHeader: { + fontWeight: 600, + p: 2, + borderBottom: 1, + borderColor: 'divider' + }, + + resultsTableContainer: { + maxHeight: 600 + }, + + resultRow: { + cursor: 'pointer', + '&:hover': { + bgcolor: 'action.hover' + } + }, + + resultQuestion: { + maxWidth: 400 + }, + + resultScore: correct => ({ + fontWeight: 'bold', + color: correct ? 'success.main' : 'error.main' + }), + + resultExpandedContent: { + py: 2.5, + px: 1.5 + }, + + resultAnswerBox: isCorrect => theme => ({ + p: 2, + mt: 1, + borderRadius: 1.5, + bgcolor: isCorrect + ? theme.palette.mode === 'dark' + ? 'rgba(46, 125, 50, 0.15)' + : 'rgba(46, 125, 50, 0.08)' + : theme.palette.mode === 'dark' + ? 'rgba(211, 47, 47, 0.15)' + : 'rgba(211, 47, 47, 0.08)', + border: `1px solid ${isCorrect ? theme.palette.success.main : theme.palette.error.main}` + }), + + resultReferenceBox: { + p: 2, + mt: 1, + borderRadius: 1.5, + bgcolor: 'action.hover' + }, + + resultJudgeBox: { + p: 2, + mt: 1, + borderRadius: 1.5, + bgcolor: 'action.hover' + }, + + // 对话框 + dialogContent: { + mt: 1 + }, + + dialogSection: { + mb: 3 + }, + + dialogDivider: { + my: 2 + }, + + dialogInfoBox: theme => ({ + p: 2, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.02)', + borderRadius: 1.5, + border: `1px solid ${theme.palette.divider}` + }), + + dialogWarning: { + mt: 1, + color: 'warning.main', + fontWeight: 500 + } +}; + +export default evalTasksStyles; diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/[datasetId]/page.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/[datasetId]/page.js new file mode 100644 index 0000000..0e27798 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/[datasetId]/page.js @@ -0,0 +1,82 @@ +'use client'; + +import { Container, Box, CircularProgress, Alert } from '@mui/material'; +import { useParams } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import useImageDatasetDetails from '../hooks/useImageDatasetDetails'; +import ImageDatasetHeader from '../components/ImageDatasetHeader'; +import DatasetContent from '../components/DatasetContent'; +import DatasetSidebar from '../components/DatasetSidebar'; + +export default function ImageDatasetDetailPage() { + const { projectId, datasetId } = useParams(); + const { t } = useTranslation(); + + const { + currentDataset, + loading, + confirming, + unconfirming, + datasetsAllCount, + datasetsConfirmCount, + updateDataset, + handleNavigate, + handleConfirm, + handleUnconfirm, + handleDelete + } = useImageDatasetDetails(projectId, datasetId); + + // 加载状态 + if (loading) { + return ( + + + + + + ); + } + + // 无数据状态 + if (!currentDataset) { + return ( + + {t('imageDatasets.notFound', '数据集不存在')} + + ); + } + + return ( + + {/* 顶部导航栏 */} + + + {/* 主要布局:左右分栏 */} + + {/* 左侧主要内容区域 */} + { + // 直接传递答案字符串,DatasetContent 已经处理了格式转换 + await updateDataset({ answer: newAnswer }); + }} + /> + + {/* 右侧固定侧边栏 */} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/DatasetContent.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/DatasetContent.js new file mode 100644 index 0000000..dab0c52 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/DatasetContent.js @@ -0,0 +1,155 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Box, Paper, Typography, Button } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import Image from 'next/image'; +import SaveIcon from '@mui/icons-material/Save'; +import AnswerInput from '../../images/components/annotation/AnswerInput'; + +function handleAnswer(dataset) { + const { answer, answerType } = dataset; + if (answerType === 'label' || answerType === 'custom_format') { + try { + return JSON.parse(answer); + } catch (e) { + return answer; + } + } + return answer; +} + +/** + * 数据集主要内容组件 + */ +export default function DatasetContent({ dataset, projectId, onAnswerChange }) { + const { t } = useTranslation(); + const [currentAnswer, setCurrentAnswer] = useState(() => handleAnswer(dataset)); + const [hasChanges, setHasChanges] = useState(false); + const [saving, setSaving] = useState(false); + + // 当 dataset 变化时,重置状态 + useEffect(() => { + setCurrentAnswer(handleAnswer(dataset)); + setHasChanges(false); + }, [dataset.id, dataset.answer]); + + // 处理答案变化 + const handleAnswerChange = newAnswer => { + setCurrentAnswer(newAnswer); + + // 检测是否有变化 + const originalAnswer = handleAnswer(dataset); + const hasChanged = JSON.stringify(newAnswer) !== JSON.stringify(originalAnswer); + setHasChanges(hasChanged); + }; + + // 保存答案 + const handleSave = async () => { + setSaving(true); + try { + let answerToSave = currentAnswer; + if (typeof answerToSave !== 'string') { + answerToSave = JSON.stringify(answerToSave, null, 2); + } + await onAnswerChange(answerToSave); + setHasChanges(false); + } catch (error) { + console.error('保存失败:', error); + } finally { + setSaving(false); + } + }; + + return ( + + + {/* 问题和保存按钮 */} + + + {dataset.question} + + + {/* 保存按钮 - 只在有变化时显示 */} + {hasChanges && ( + + )} + + + {/* 答案编辑器 */} + + + {/* 图片 */} + + + {dataset.base64 ? ( + {dataset.imageName} + ) : ( + {dataset.imageName} + )} + + + {dataset.imageName} + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/DatasetSidebar.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/DatasetSidebar.js new file mode 100644 index 0000000..ec8bc85 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/DatasetSidebar.js @@ -0,0 +1,28 @@ +'use client'; + +import { Box } from '@mui/material'; +import MetadataInfo from './MetadataInfo'; +import MetadataEditor from './MetadataEditor'; + +/** + * 数据集右侧边栏组件 + */ +export default function DatasetSidebar({ dataset, projectId, onUpdate }) { + return ( + + {/* 元数据信息 - Chip 形式 */} + + + {/* 操作卡片 */} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/EmptyState.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/EmptyState.js new file mode 100644 index 0000000..e5a87e1 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/EmptyState.js @@ -0,0 +1,24 @@ +'use client'; + +import { Box, Typography } from '@mui/material'; +import ImageSearchIcon from '@mui/icons-material/ImageSearch'; +import { useTranslation } from 'react-i18next'; +import { imageDatasetStyles } from '../styles/imageDatasetStyles'; + +export default function EmptyState() { + const { t } = useTranslation(); + + return ( + + + + + + {t('imageDatasets.noData', { defaultValue: '暂无图片数据集' })} + + + {t('imageDatasets.noDataTip', { defaultValue: '请先在图片管理中生成问答数据集' })} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ExportImageDatasetDialog.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ExportImageDatasetDialog.js new file mode 100644 index 0000000..188fb90 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ExportImageDatasetDialog.js @@ -0,0 +1,127 @@ +'use client'; + +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + FormControl, + FormLabel, + RadioGroup, + FormControlLabel, + Radio, + Checkbox, + TextField, + Box, + Typography, + Alert +} from '@mui/material'; + +const ExportImageDatasetDialog = ({ open, onClose, onExport }) => { + const { t } = useTranslation(); + const [formatType, setFormatType] = useState('raw'); + const [exportImages, setExportImages] = useState(false); + const [includeImagePath, setIncludeImagePath] = useState(true); + const [systemPrompt, setSystemPrompt] = useState(''); + const [confirmedOnly, setConfirmedOnly] = useState(false); + + const handleExport = () => { + onExport({ + formatType, + exportImages, + includeImagePath, + systemPrompt, + confirmedOnly + }); + }; + + const handleClose = () => { + onClose(); + }; + + return ( + + {t('imageDatasets.exportTitle', '导出图片数据集')} + + + {/* 导出格式选择 */} + + {t('imageDatasets.exportFormat', '导出格式')} + setFormatType(e.target.value)}> + } label={t('imageDatasets.rawFormat', '原始格式')} /> + } label="ShareGPT (OpenAI)" /> + } label="Alpaca" /> + + + + {/* 图片导出选项 */} + + setExportImages(e.target.checked)} />} + label={t('imageDatasets.exportImagesOption', '导出图片文件')} + /> + + {t('imageDatasets.exportImagesDesc', '将所有图片打包成 ZIP 压缩包一起下载')} + + + + {/* 图片路径选项 */} + + setIncludeImagePath(e.target.checked)} />} + label={t('imageDatasets.includeImagePath', '在数据集中包含图片路径')} + /> + + {t('imageDatasets.includeImagePathDesc', '在问题或答案中添加图片路径(格式:/images/图片名称)')} + + + + {/* 系统提示词 */} + setSystemPrompt(e.target.value)} + placeholder={t('imageDatasets.systemPromptPlaceholder', '输入系统提示词...')} + fullWidth + /> + + {/* 仅导出已确认 */} + setConfirmedOnly(e.target.checked)} />} + label={t('imageDatasets.confirmedOnly', '仅导出已确认的数据集')} + /> + + {/* 提示信息 */} + + {t('imageDatasets.exportTip', '标签格式的答案将自动解析为文本(逗号分隔)')} + + + + + + + + + ); +}; + +export default ExportImageDatasetDialog; diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetCard.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetCard.js new file mode 100644 index 0000000..8922624 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetCard.js @@ -0,0 +1,206 @@ +'use client'; + +import { Card, CardMedia, Box, Chip, Typography, Tooltip, IconButton } from '@mui/material'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import { useTranslation } from 'react-i18next'; +import { imageDatasetStyles } from '../styles/imageDatasetStyles'; + +export default function ImageDatasetCard({ + dataset, + onClick, + onView = () => {}, + onDelete = () => {}, + onEvaluate = () => {} +}) { + const { t } = useTranslation(); + + const getAnswerText = () => { + if (!dataset.answer) return t('imageDatasets.noAnswer', '暂无答案'); + if (dataset.answerType === 'label') { + try { + const labels = JSON.parse(dataset.answer); + return `${t('imageDatasets.labels', '标签')}: ${labels.join(', ')}`; + } catch { + return dataset.answer; + } + } + return dataset.answer; + }; + + const getAnswerTypeLabel = type => { + switch (type) { + case 'label': + return t('imageDatasets.typeLabel', '标签'); + case 'custom_format': + return t('imageDatasets.typeCustom', '自定义'); + default: + return t('imageDatasets.typeText', '文本'); + } + }; + + const getAnswerTypeColor = type => { + switch (type) { + case 'label': + return 'secondary'; + case 'custom_format': + return 'info'; + default: + return 'primary'; + } + }; + + const getScoreLabel = () => { + if (!dataset.score || dataset.score === 0) { + return t('imageDatasets.unscored', '未评分'); + } + return dataset.score; + }; + + return ( + + {/* 图片区域 */} + + + + {/* 悬停遮罩 */} + + + {/* 问题内容 - 底部,毛玻璃背景 */} + + + {dataset.question} + + + + + {/* 内容区域 - 标签和操作按钮 */} + + + + {/* 左侧:所有标签 */} + + + + ⭐} + label={getScoreLabel()} + size="small" + color={dataset.score && dataset.score > 0 ? 'warning' : 'default'} + sx={{ fontSize: '0.7rem', height: 20 }} + /> + + + {/* 右侧:操作按钮 - 不同颜色 */} + + + { + e.stopPropagation(); + onView(dataset.id); + }} + sx={{ + p: 0.5, + borderRadius: 1, + color: '#1976d2', + '&:hover': { + backgroundColor: 'rgba(25, 118, 210, 0.1)' + } + }} + > + + + + + + { + e.stopPropagation(); + onEvaluate(dataset.id); + }} + sx={{ + p: 0.5, + borderRadius: 1, + color: '#f57c00', + '&:hover': { + backgroundColor: 'rgba(245, 124, 0, 0.1)' + } + }} + > + + + + + + { + e.stopPropagation(); + onDelete(dataset.id); + }} + sx={{ + p: 0.5, + borderRadius: 1, + color: '#d32f2f', + '&:hover': { + backgroundColor: 'rgba(211, 47, 47, 0.1)' + } + }} + > + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetFilterDialog.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetFilterDialog.js new file mode 100644 index 0000000..7581eff --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetFilterDialog.js @@ -0,0 +1,87 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Box, + Typography, + Select, + MenuItem, + Slider, + TextField, + Button +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export default function ImageDatasetFilterDialog({ + open, + onClose, + statusFilter, + scoreFilter, + onStatusChange, + onScoreChange, + onResetFilters, + onApplyFilters +}) { + const { t } = useTranslation(); + + return ( + + {t('datasets.filtersTitle', '筛选条件')} + + {/* 确认状态筛选 */} + + + {t('imageDatasets.status', { defaultValue: '确认状态' })} + + + + + {/* 评分范围筛选 */} + + + {t('imageDatasets.scoreRange', { defaultValue: '评分范围' })} + + + {scoreFilter[0]} - {scoreFilter[1]} 分 + + onScoreChange(newValue)} + valueLabelDisplay="auto" + min={0} + max={5} + step={1} + marks + sx={{ mt: 1 }} + /> + + + + {/* 对话框操作按钮 */} + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetFilters.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetFilters.js new file mode 100644 index 0000000..b4dc57a --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetFilters.js @@ -0,0 +1,48 @@ +'use client'; + +import { Box, Paper, IconButton, InputBase, Button, Badge } from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import { useTranslation } from 'react-i18next'; + +export default function ImageDatasetFilters({ + searchQuery, + onSearchChange, + onMoreFiltersClick, + activeFilterCount = 0 +}) { + const { t } = useTranslation(); + + return ( + + {/* 搜索框 - 完全参考数据集管理的设计 */} + + + + + onSearchChange(e.target.value)} + /> + + + {/* 更多筛选按钮 - 带 Badge 显示活跃筛选条件数 */} + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetHeader.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetHeader.js new file mode 100644 index 0000000..b226237 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/ImageDatasetHeader.js @@ -0,0 +1,82 @@ +'use client'; + +import { Box, Button, Divider, Typography, IconButton, CircularProgress, Paper } from '@mui/material'; +import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import DeleteIcon from '@mui/icons-material/Delete'; +import UndoIcon from '@mui/icons-material/Undo'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/navigation'; + +/** + * 图片数据集详情页面的头部导航组件 + */ +export default function ImageDatasetHeader({ + projectId, + datasetsAllCount, + datasetsConfirmCount, + confirming, + unconfirming, + currentDataset, + onNavigate, + onConfirm, + onUnconfirm, + onDelete +}) { + const router = useRouter(); + const { t } = useTranslation(); + + return ( + + + {/* 左侧:返回按钮和统计信息 */} + + + + + 共 {datasetsAllCount} 个数据集,已确认 {datasetsConfirmCount} 个 ( + {datasetsAllCount > 0 ? ((datasetsConfirmCount / datasetsAllCount) * 100).toFixed(2) : 0}%) + + + + {/* 右侧:翻页、确认/取消确认、删除按钮 */} + + onNavigate('prev')}> + + + onNavigate('next')}> + + + + + {/* 确认/取消确认按钮 */} + {currentDataset?.confirmed ? ( + + ) : ( + + )} + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/MetadataEditor.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/MetadataEditor.js new file mode 100644 index 0000000..8d113bc --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/MetadataEditor.js @@ -0,0 +1,159 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Box, Typography, Divider, Paper } from '@mui/material'; +import { toast } from 'sonner'; +import StarRating from '@/components/datasets/StarRating'; +import TagSelector from '@/components/datasets/TagSelector'; +import NoteInput from '@/components/datasets/NoteInput'; +import { useTranslation } from 'react-i18next'; + +export default function MetadataEditor({ dataset, projectId, onUpdate }) { + const { t } = useTranslation(); + const [availableTags, setAvailableTags] = useState([]); + const [loading, setLoading] = useState(false); + + // 解析数据集中的标签 + const parseDatasetTags = tagsString => { + try { + return JSON.parse(tagsString || '[]'); + } catch (e) { + return []; + } + }; + + // 本地状态管理,从 props 初始化 + const [localScore, setLocalScore] = useState(dataset?.score || 0); + const [localTags, setLocalTags] = useState(() => { + const tags = parseDatasetTags(dataset?.tags); + // 确保 localTags 始终是数组 + return Array.isArray(tags) ? tags : []; + }); + const [localNote, setLocalNote] = useState(dataset?.note || ''); + + // 获取项目中已使用的标签 + useEffect(() => { + const fetchAvailableTags = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/image-datasets/tags`); + if (response.ok) { + const data = await response.json(); + setAvailableTags(data.tags || []); + } + } catch (error) { + console.error('获取可用标签失败:', error); + } + }; + + if (projectId) { + fetchAvailableTags(); + } + }, [projectId]); + + // 同步props中的dataset到本地状态 + useEffect(() => { + if (dataset) { + setLocalScore(dataset.score || 0); + const tags = parseDatasetTags(dataset.tags); + setLocalTags(Array.isArray(tags) ? tags : []); + setLocalNote(dataset.note || ''); + } + }, [dataset]); + + // 更新数据集元数据 + const updateMetadata = async updates => { + if (loading) return; + + // 立即更新本地状态,提升响应速度 + if (updates.score !== undefined) { + setLocalScore(updates.score); + } + // 注意:tags 已经在 handleTagsChange 中更新过了,这里不需要再更新 + if (updates.note !== undefined) { + setLocalNote(updates.note); + } + + setLoading(true); + try { + // 调用父组件的更新方法 + if (onUpdate) { + await onUpdate(updates); + } + toast.success(t('imageDatasets.updateSuccess', '更新成功')); + } catch (error) { + console.error('更新数据集元数据失败:', error); + toast.error(t('imageDatasets.updateFailed', '更新失败')); + + // 出错时恢复本地状态 + if (updates.score !== undefined) { + setLocalScore(dataset?.score || 0); + } + if (updates.tags !== undefined) { + // 恢复为原始的标签数组 + const tags = parseDatasetTags(dataset?.tags); + setLocalTags(Array.isArray(tags) ? tags : []); + } + if (updates.note !== undefined) { + setLocalNote(dataset?.note || ''); + } + } finally { + setLoading(false); + } + }; + + // 处理评分变更 + const handleScoreChange = newScore => { + updateMetadata({ score: newScore }); + }; + + // 处理标签变更 + const handleTagsChange = newTags => { + // 立即更新本地状态(保持为数组) + setLocalTags(newTags); + // 发送给父组件时转换为 JSON 字符串 + updateMetadata({ tags: JSON.stringify(newTags) }); + }; + + // 处理备注变更 + const handleNoteChange = newNote => { + updateMetadata({ note: newNote }); + }; + + return ( + + {/* 评分区域 */} + + + {t('datasets.rating', '评分')} + + + + + + + {/* 标签区域 */} + + + {t('datasets.customTags', '自定义标签')} + + + + + + + {/* 备注区域 */} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/components/MetadataInfo.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/MetadataInfo.js new file mode 100644 index 0000000..22ac4e0 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/components/MetadataInfo.js @@ -0,0 +1,154 @@ +'use client'; + +import { Box, Typography, Chip, alpha, Divider } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useTheme } from '@mui/material/styles'; + +/** + * 元数据信息展示组件 - Chip 形式(参考 DatasetMetadata) + */ +export default function MetadataInfo({ dataset }) { + const { t } = useTranslation(); + const theme = useTheme(); + + // 解析标签 + const parsedTags = (() => { + try { + if (typeof dataset.tags === 'string' && dataset.tags) { + return JSON.parse(dataset.tags); + } + return Array.isArray(dataset.tags) ? dataset.tags : []; + } catch { + return []; + } + })(); + + // 格式化文件大小 + const formatFileSize = bytes => { + if (!bytes) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; + }; + + return ( + + {/* 数据集信息 */} + + {t('common.detailInfo', '详细信息')} + + + {/* 使用模型 */} + {dataset.model && ( + + )} + + {/* 标签数量 */} + {parsedTags.length > 0 && ( + + )} + + {/* 创建时间 */} + + + {/* 文本块信息 */} + {dataset.questionTemplate?.description && ( + + )} + + {/* 确认状态 */} + {dataset.confirmed && ( + + )} + + + {/* 图片信息 */} + {dataset.image && ( + <> + + + {t('images.imageInfo', '图片信息')} + + + {/* 图片尺寸 */} + {dataset.image.width && dataset.image.height && ( + + )} + + {/* 文件大小 */} + {dataset.image.size && ( + + )} + + {/* 图片创建时间 */} + {dataset.image.createAt && ( + + )} + + {/* 图片名称 */} + {dataset.image.imageName && ( + + )} + + + )} + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetail.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetail.js new file mode 100644 index 0000000..4a382df --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetail.js @@ -0,0 +1,77 @@ +import { useState, useEffect, useCallback } from 'react'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; + +export function useImageDatasetDetail(projectId, datasetId) { + const { t } = useTranslation(); + const [dataset, setDataset] = useState(null); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + + // 获取详情 + const fetchDetail = useCallback(async () => { + try { + setLoading(true); + const response = await axios.get(`/api/projects/${projectId}/image-datasets/${datasetId}`); + setDataset(response.data); + } catch (error) { + console.error('Failed to fetch dataset detail:', error); + toast.error(t('imageDatasets.fetchDetailFailed', { defaultValue: '获取详情失败' })); + } finally { + setLoading(false); + } + }, [projectId, datasetId, t]); + + // 更新数据集 + const updateDataset = useCallback( + async updates => { + try { + setSaving(true); + const response = await axios.put(`/api/projects/${projectId}/image-datasets/${datasetId}`, updates); + setDataset(response.data); + toast.success(t('imageDatasets.updateSuccess', { defaultValue: '更新成功' })); + return response.data; + } catch (error) { + console.error('Failed to update dataset:', error); + toast.error(t('imageDatasets.updateFailed', { defaultValue: '更新失败' })); + throw error; + } finally { + setSaving(false); + } + }, + [projectId, datasetId, t] + ); + + // AI 重新识别 + const regenerateAnswer = useCallback(async () => { + try { + setSaving(true); + const response = await axios.post(`/api/projects/${projectId}/image-datasets/${datasetId}/regenerate`); + setDataset(response.data); + toast.success(t('imageDatasets.regenerateSuccess', { defaultValue: 'AI 识别成功' })); + return response.data; + } catch (error) { + console.error('Failed to regenerate answer:', error); + toast.error(t('imageDatasets.regenerateFailed', { defaultValue: 'AI 识别失败' })); + throw error; + } finally { + setSaving(false); + } + }, [projectId, datasetId, t]); + + useEffect(() => { + if (projectId && datasetId) { + fetchDetail(); + } + }, [projectId, datasetId, fetchDetail]); + + return { + dataset, + loading, + saving, + updateDataset, + regenerateAnswer, + fetchDetail + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetails.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetails.js new file mode 100644 index 0000000..259875a --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetDetails.js @@ -0,0 +1,172 @@ +'use client'; + +import { useState, useEffect, useCallback } from 'react'; +import { useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import axios from 'axios'; + +export default function useImageDatasetDetails(projectId, datasetId) { + const router = useRouter(); + const { t } = useTranslation(); + + const [currentDataset, setCurrentDataset] = useState(null); + const [loading, setLoading] = useState(true); + const [confirming, setConfirming] = useState(false); + const [unconfirming, setUnconfirming] = useState(false); + const [saving, setSaving] = useState(false); + const [datasetsAllCount, setDatasetsAllCount] = useState(0); + const [datasetsConfirmCount, setDatasetsConfirmCount] = useState(0); + + // 获取数据集列表信息 + const fetchDatasetsList = useCallback(async () => { + try { + // 获取所有数据集以正确统计已确认数量 + const response = await axios.get(`/api/projects/${projectId}/image-datasets?page=1&pageSize=10000`); + const data = response.data; + setDatasetsAllCount(data.total || 0); + setDatasetsConfirmCount(data.data?.filter(d => d.confirmed).length || 0); + } catch (error) { + console.error('Failed to fetch datasets list:', error); + } + }, [projectId]); + + // 获取当前数据集详情 + const fetchDatasetDetail = useCallback(async () => { + try { + setLoading(true); + const response = await axios.get(`/api/projects/${projectId}/image-datasets/${datasetId}`); + setCurrentDataset(response.data); + } catch (error) { + console.error('Failed to fetch dataset detail:', error); + toast.error(t('imageDatasets.fetchDetailFailed', '获取详情失败')); + } finally { + setLoading(false); + } + }, [projectId, datasetId, t]); + + useEffect(() => { + if (projectId && datasetId) { + fetchDatasetDetail(); + fetchDatasetsList(); + } + }, [projectId, datasetId, fetchDatasetDetail, fetchDatasetsList]); + + // 更新数据集 + const updateDataset = useCallback( + async updates => { + try { + setSaving(true); + await axios.put(`/api/projects/${projectId}/image-datasets/${datasetId}`, updates); + toast.success(t('imageDatasets.updateSuccess', '更新成功')); + // 刷新数据 + await fetchDatasetDetail(); + await fetchDatasetsList(); + } catch (error) { + console.error('Failed to update dataset:', error); + toast.error(t('imageDatasets.updateFailed', '更新失败')); + } finally { + setSaving(false); + } + }, + [projectId, datasetId, t, fetchDatasetDetail, fetchDatasetsList] + ); + + // 翻页导航 + const handleNavigate = useCallback( + async (direction, skipCurrentId = null) => { + try { + // 获取所有数据集(不分页),使用一个足够大的 pageSize + const response = await axios.get(`/api/projects/${projectId}/image-datasets?page=1&pageSize=10000`); + const datasets = response.data.data || []; + + if (datasets.length === 0) { + router.push(`/projects/${projectId}/image-datasets`); + return; + } + + // 确定当前索引 + let currentIndex = -1; + const searchId = skipCurrentId || datasetId; + const currentDatasetId = String(searchId); + + // 查找当前数据集的索引 + currentIndex = datasets.findIndex(d => String(d.id) === currentDatasetId); + + // 如果找不到(删除场景或其他原因),从第一个开始 + if (currentIndex === -1) { + currentIndex = 0; + } + + // 计算下一个索引 + let nextIndex; + if (direction === 'prev') { + nextIndex = currentIndex > 0 ? currentIndex - 1 : datasets.length - 1; + } else { + nextIndex = currentIndex < datasets.length - 1 ? currentIndex + 1 : 0; + } + + const nextDataset = datasets[nextIndex]; + if (nextDataset) { + router.push(`/projects/${projectId}/image-datasets/${nextDataset.id}`); + } + } catch (error) { + console.error('Failed to navigate:', error); + toast.error(t('common.navigationFailed', '导航失败')); + } + }, + [projectId, datasetId, router, t] + ); + + // 确认保留 + const handleConfirm = useCallback(async () => { + setConfirming(true); + try { + await updateDataset({ confirmed: true }); + // 确认后导航到下一条 + await handleNavigate('next'); + } finally { + setConfirming(false); + } + }, [updateDataset, handleNavigate]); + + // 取消确认 + const handleUnconfirm = useCallback(async () => { + setUnconfirming(true); + try { + await updateDataset({ confirmed: false }); + } finally { + setUnconfirming(false); + } + }, [updateDataset]); + + // 删除数据集 + const handleDelete = useCallback(async () => { + if (confirm(t('imageDatasets.deleteConfirm', '确定要删除这个数据集吗?'))) { + try { + await axios.delete(`/api/projects/${projectId}/image-datasets/${datasetId}`); + toast.success(t('imageDatasets.deleteSuccess', '删除成功')); + // 导航到下一条,传递 datasetId 以便 handleNavigate 知道是删除场景 + await handleNavigate('next', datasetId); + } catch (error) { + console.error('Failed to delete dataset:', error); + toast.error(t('imageDatasets.deleteFailed', '删除失败')); + } + } + }, [projectId, datasetId, handleNavigate, t]); + + return { + currentDataset, + loading, + saving, + confirming, + unconfirming, + datasetsAllCount, + datasetsConfirmCount, + updateDataset, + handleNavigate, + handleConfirm, + handleUnconfirm, + handleDelete + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetExport.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetExport.js new file mode 100644 index 0000000..76577d2 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetExport.js @@ -0,0 +1,195 @@ +'use client'; + +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import axios from 'axios'; + +const useImageDatasetExport = projectId => { + const { t } = useTranslation(); + + /** + * 解析标签格式的答案 + * 如果答案是 JSON 数组格式,解析并用逗号连接 + */ + const parseAnswerLabels = item => { + const { answer, answerType } = item; + if (answerType !== 'label' || !answer) { + return answer; + } + + try { + // 尝试解析 JSON + const parsed = JSON.parse(answer); + if (Array.isArray(parsed)) { + // 如果是数组,用逗号连接 + return parsed.join(', '); + } + return answer; + } catch (e) { + // 不是 JSON 格式,直接返回原答案 + return answer; + } + }; + + /** + * 导出图片数据集 + */ + const exportImageDatasets = async exportOptions => { + try { + // 1. 获取数据集数据 + const apiUrl = `/api/projects/${projectId}/image-datasets/export`; + const response = await axios.post(apiUrl, { + confirmedOnly: exportOptions.confirmedOnly + }); + + let datasets = response.data; + + if (!datasets || datasets.length === 0) { + toast.warning(t('imageDatasets.noDataToExport', '没有可导出的数据')); + return false; + } + + // 2. 处理答案中的标签格式 + datasets = datasets.map(item => ({ + ...item, + answer: parseAnswerLabels(item) + })); + + // 3. 根据格式类型转换数据 + let formattedData; + + if (exportOptions.formatType === 'raw') { + // 原始格式:直接导出数据集 + formattedData = datasets.map(item => { + const result = { ...item }; + + // 如果需要包含图片路径 + if (exportOptions.includeImagePath && item.imageName) { + result.image_path = `/images/${item.imageName}`; + } + + if (item.answerType === 'custom_format') { + try { + result.answerObj = JSON.parse(item.answer); + } catch {} + } + + return result; + }); + } else if (exportOptions.formatType === 'alpaca') { + formattedData = datasets.map(({ question, answer, imageName }) => { + const item = { + instruction: question, + input: '', + output: answer + }; + + // 如果需要包含图片路径 + if (exportOptions.includeImagePath && imageName) { + item.images = [`/images/${imageName}`]; + } + + return item; + }); + } else if (exportOptions.formatType === 'sharegpt') { + formattedData = datasets.map(({ question, answer, imageName }) => { + const messages = []; + + // 添加系统提示词(如果有) + if (exportOptions.systemPrompt) { + messages.push({ + role: 'system', + content: exportOptions.systemPrompt + }); + } + + // 添加用户问题 + const userContent = []; + + // 如果需要包含图片路径 + if (exportOptions.includeImagePath && imageName) { + userContent.push({ + type: 'image_url', + image_url: { + url: `/images/${imageName}` + } + }); + } + + userContent.push({ + type: 'text', + text: question + }); + + messages.push({ + role: 'user', + content: userContent + }); + + // 添加助手回答 + messages.push({ + role: 'assistant', + content: answer + }); + + return { messages }; + }); + } + + // 4. 生成 JSON 文件 + const jsonContent = JSON.stringify(formattedData, null, 2); + const blob = new Blob([jsonContent], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + + const formatSuffix = exportOptions.formatType; + const dateStr = new Date().toISOString().slice(0, 10); + a.download = `image-datasets-${projectId}-${formatSuffix}-${dateStr}.json`; + + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(url); + + toast.success(t('imageDatasets.exportSuccess', '数据集导出成功')); + + // 5. 如果需要导出图片,调用压缩包接口 + if (exportOptions.exportImages) { + try { + const params = new URLSearchParams({ + confirmedOnly: exportOptions.confirmedOnly.toString() + }); + + const zipUrl = `/api/projects/${projectId}/image-datasets/export-zip?${params.toString()}`; + + // 创建一个隐藏的 a 标签来触发下载 + const a = document.createElement('a'); + a.href = zipUrl; + a.style.display = 'none'; + a.target = '_blank'; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + toast.success(t('imageDatasets.exportImagesSuccess', '图片压缩包导出成功')); + } catch (error) { + console.error('Failed to export images:', error); + toast.error(t('imageDatasets.exportImagesFailed', '图片导出失败')); + } + } + + return true; + } catch (error) { + console.error('Export failed:', error); + toast.error(error.message || t('imageDatasets.exportFailed', '导出失败')); + return false; + } + }; + + return { + exportImageDatasets + }; +}; + +export default useImageDatasetExport; diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetFilters.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetFilters.js new file mode 100644 index 0000000..6a5329a --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasetFilters.js @@ -0,0 +1,71 @@ +import { useState, useEffect, useCallback } from 'react'; + +const STORAGE_KEY = 'imageDatasetFilters'; + +export function useImageDatasetFilters(projectId) { + const [searchQuery, setSearchQuery] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); + const [scoreFilter, setScoreFilter] = useState([0, 5]); + const [isInitialized, setIsInitialized] = useState(false); + + // 从 localStorage 恢复筛选条件 + useEffect(() => { + try { + const stored = localStorage.getItem(`${STORAGE_KEY}_${projectId}`); + if (stored) { + const filters = JSON.parse(stored); + setSearchQuery(filters.searchQuery || ''); + setStatusFilter(filters.statusFilter || 'all'); + setScoreFilter(filters.scoreFilter || [0, 5]); + } + } catch (error) { + console.error('Failed to restore filters:', error); + } + setIsInitialized(true); + }, [projectId]); + + // 保存筛选条件到 localStorage + useEffect(() => { + if (isInitialized) { + try { + localStorage.setItem( + `${STORAGE_KEY}_${projectId}`, + JSON.stringify({ + searchQuery, + statusFilter, + scoreFilter + }) + ); + } catch (error) { + console.error('Failed to save filters:', error); + } + } + }, [projectId, searchQuery, statusFilter, scoreFilter, isInitialized]); + + // 计算活跃筛选条件数 + const getActiveFilterCount = useCallback(() => { + let count = 0; + if (statusFilter !== 'all') count++; + if (scoreFilter[0] > 0 || scoreFilter[1] < 5) count++; + return count; + }, [statusFilter, scoreFilter]); + + // 重置筛选条件 + const resetFilters = useCallback(() => { + setSearchQuery(''); + setStatusFilter('all'); + setScoreFilter([0, 5]); + }, []); + + return { + searchQuery, + setSearchQuery, + statusFilter, + setStatusFilter, + scoreFilter, + setScoreFilter, + isInitialized, + getActiveFilterCount, + resetFilters + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasets.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasets.js new file mode 100644 index 0000000..fe52a73 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/hooks/useImageDatasets.js @@ -0,0 +1,90 @@ +import { useState, useEffect, useCallback, useMemo } from 'react'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; + +export function useImageDatasets(projectId, filters = {}) { + const { t } = useTranslation(); + const [datasets, setDatasets] = useState({ data: [], total: 0 }); + const [loading, setLoading] = useState(false); + const [page, setPage] = useState(1); + const pageSize = 20; + + // 使用 useMemo 稳定 filters 对象引用 + const stableFilters = useMemo( + () => ({ + search: filters.search || '', + confirmed: filters.confirmed, + minScore: filters.minScore, + maxScore: filters.maxScore + }), + [filters.search, filters.confirmed, filters.minScore, filters.maxScore] + ); + + // 获取数据集列表 + const fetchDatasets = useCallback(async () => { + try { + setLoading(true); + let url = `/api/projects/${projectId}/image-datasets?page=${page}&pageSize=${pageSize}`; + + // 搜索条件 + if (stableFilters.search) { + url += `&search=${encodeURIComponent(stableFilters.search)}`; + } + + // 确认状态筛选 + if (stableFilters.confirmed !== undefined) { + url += `&confirmed=${stableFilters.confirmed}`; + } + + // 评分筛选 + if (stableFilters.minScore !== undefined || stableFilters.maxScore !== undefined) { + if (stableFilters.minScore !== undefined) { + url += `&minScore=${stableFilters.minScore}`; + } + if (stableFilters.maxScore !== undefined) { + url += `&maxScore=${stableFilters.maxScore}`; + } + } + + const response = await axios.get(url); + setDatasets(response.data); + } catch (error) { + console.error('Failed to fetch datasets:', error); + toast.error(t('imageDatasets.fetchFailed', { defaultValue: '获取数据集失败' })); + } finally { + setLoading(false); + } + }, [projectId, page, pageSize, stableFilters, t]); + + // 删除数据集 + const deleteDataset = useCallback( + async datasetId => { + try { + await axios.delete(`/api/projects/${projectId}/image-datasets/${datasetId}`); + toast.success(t('imageDatasets.deleteSuccess', { defaultValue: '删除成功' })); + fetchDatasets(); + } catch (error) { + console.error('Failed to delete dataset:', error); + toast.error(t('imageDatasets.deleteFailed', { defaultValue: '删除失败' })); + } + }, + [projectId, fetchDatasets, t] + ); + + useEffect(() => { + if (projectId) { + fetchDatasets(); + } + }, [projectId, page, stableFilters, fetchDatasets]); + + return { + datasets, + loading, + page, + setPage, + pageSize, + fetchDatasets, + deleteDataset + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/page.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/page.js new file mode 100644 index 0000000..07d2226 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/page.js @@ -0,0 +1,193 @@ +'use client'; + +import { useState } from 'react'; +import { Container, Box, Typography, Grid, Pagination, CircularProgress, Card, Button } from '@mui/material'; +import { useParams, useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { imageDatasetStyles } from './styles/imageDatasetStyles'; +import { useImageDatasets } from './hooks/useImageDatasets'; +import { useImageDatasetFilters } from './hooks/useImageDatasetFilters'; +import ImageDatasetFilters from './components/ImageDatasetFilters'; +import ImageDatasetFilterDialog from './components/ImageDatasetFilterDialog'; +import ImageDatasetCard from './components/ImageDatasetCard'; +import EmptyState from './components/EmptyState'; +import ExportImageDatasetDialog from './components/ExportImageDatasetDialog'; +import useImageDatasetExport from './hooks/useImageDatasetExport'; +import FileDownloadIcon from '@mui/icons-material/FileDownload'; +import { alpha } from '@mui/material/styles'; + +export default function ImageDatasetsPage() { + const { projectId } = useParams(); + const router = useRouter(); + const { t } = useTranslation(); + const [filterDialogOpen, setFilterDialogOpen] = useState(false); + const [exportDialogOpen, setExportDialogOpen] = useState(false); + + // 使用筛选 Hook + const { + searchQuery, + setSearchQuery, + statusFilter, + setStatusFilter, + scoreFilter, + setScoreFilter, + getActiveFilterCount, + resetFilters + } = useImageDatasetFilters(projectId); + + // 使用数据 Hook + const { datasets, loading, page, setPage, pageSize, fetchDatasets } = useImageDatasets(projectId, { + search: searchQuery, + confirmed: statusFilter === 'all' ? undefined : statusFilter === 'confirmed', + minScore: scoreFilter[0], + maxScore: scoreFilter[1] + }); + + // 使用导出 Hook + const { exportImageDatasets } = useImageDatasetExport(projectId); + + const handlePageChange = (event, value) => { + setPage(value); + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + const handleCardClick = datasetId => { + router.push(`/projects/${projectId}/image-datasets/${datasetId}`); + }; + + const handleViewDetails = datasetId => { + router.push(`/projects/${projectId}/image-datasets/${datasetId}`); + }; + + const handleDeleteDataset = async datasetId => { + if (confirm(t('imageDatasets.deleteConfirm', '确定要删除这个数据集吗?'))) { + try { + await axios.delete(`/api/projects/${projectId}/image-datasets/${datasetId}`); + toast.success(t('imageDatasets.deleteSuccess', '删除成功')); + // 重新查询数据 + fetchDatasets(); + } catch (error) { + console.error('Failed to delete dataset:', error); + toast.error(t('imageDatasets.deleteFailed', '删除失败')); + } + } + }; + + const handleEvaluateDataset = datasetId => { + toast.info(t('common.comingSoon', '功能开发中...')); + }; + + const handleResetFilters = () => { + resetFilters(); + setFilterDialogOpen(false); + }; + + const handleApplyFilters = () => { + setFilterDialogOpen(false); + setPage(1); + }; + + const handleExport = async exportOptions => { + setExportDialogOpen(false); + await exportImageDatasets(exportOptions); + }; + + const totalPages = Math.ceil(datasets.total / pageSize); + + return ( + + {/* 筛选区域 - 参考数据集管理的设计 */} + alpha(theme.palette.primary.main, 0.06) + }} + > + + { + setSearchQuery(value); + setPage(1); + }} + onMoreFiltersClick={() => setFilterDialogOpen(true)} + activeFilterCount={getActiveFilterCount()} + /> + + + + + {/* 数据集列表 */} + {loading ? ( + + + + ) : datasets.data.length === 0 ? ( + + ) : ( + <> + + {datasets.data.map(dataset => ( + + + + ))} + + + {/* 分页 */} + {totalPages > 1 && ( + + + + )} + + )} + + {/* 筛选对话框 */} + setFilterDialogOpen(false)} + statusFilter={statusFilter} + scoreFilter={scoreFilter} + onStatusChange={setStatusFilter} + onScoreChange={setScoreFilter} + onResetFilters={handleResetFilters} + onApplyFilters={handleApplyFilters} + /> + + {/* 导出对话框 */} + setExportDialogOpen(false)} + onExport={handleExport} + /> + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/image-datasets/styles/imageDatasetStyles.js b/easy-dataset-main/app/projects/[projectId]/image-datasets/styles/imageDatasetStyles.js new file mode 100644 index 0000000..fe0368c --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/image-datasets/styles/imageDatasetStyles.js @@ -0,0 +1,266 @@ +/** + * 图片数据集模块样式配置 + * 参考图片管理模块的精美设计 + */ + +export const imageDatasetStyles = { + // 页面容器 + pageContainer: { + py: 4 + }, + + // 页面头部 + header: { + mb: 4, + display: 'flex', + justifyContent: 'space-between', + alignItems: 'flex-start', + flexWrap: 'wrap', + gap: 3 + }, + + headerTitle: { + display: 'flex', + flexDirection: 'column', + gap: 0.5 + }, + + title: { + fontWeight: 700 + }, + + subtitle: { + color: 'text.secondary', + fontSize: '0.875rem' + }, + + headerActions: { + display: 'flex', + gap: 2, + flexWrap: 'wrap' + }, + + // 筛选区域 + filterCard: { + mb: 3, + borderRadius: 2, + boxShadow: 1, + border: '1px solid', + borderColor: 'divider', + overflow: 'visible' + }, + + filterContent: { + display: 'flex', + gap: 2, + alignItems: 'center', + flexWrap: 'wrap' + }, + + // 数据集卡片 - 参考图片管理的设计 + datasetCard: { + height: '100%', + display: 'flex', + flexDirection: 'column', + borderRadius: 3, + overflow: 'hidden', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + border: '1px solid', + borderColor: 'divider', + bgcolor: 'background.paper', + cursor: 'pointer', + '&:hover': { + transform: 'translateY(-8px)', + boxShadow: theme => `0 12px 24px ${theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.4)' : 'rgba(0,0,0,0.15)'}`, + borderColor: 'primary.main', + '& .image-overlay': { + opacity: 1 + }, + '& .image-media': { + transform: 'scale(1.05)' + } + } + }, + + // 图片包装器 + imageWrapper: { + position: 'relative', + overflow: 'hidden', + bgcolor: 'grey.100' + }, + + // 图片媒体 + imageMedia: { + className: 'image-media', + height: 220, + objectFit: 'cover', + transition: 'transform 0.3s ease' + }, + + // 悬停遮罩 + imageOverlay: { + className: 'image-overlay', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: + 'linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 40%, transparent 60%, rgba(0,0,0,0.6) 100%)', + opacity: 0, + transition: 'opacity 0.3s ease', + pointerEvents: 'none' + }, + + // 状态标签容器 - 右上角 + statusChipsContainer: { + position: 'absolute', + top: 12, + right: 12, + display: 'flex', + gap: 0.5, + flexDirection: 'column', + alignItems: 'flex-end', + zIndex: 2 + }, + + // 状态标签 + statusChip: { + backdropFilter: 'blur(10px)', + fontWeight: 600, + fontSize: '0.75rem', + height: 24, + boxShadow: 2 + }, + + // 图片名称容器 - 底部 + imageNameContainer: { + position: 'absolute', + bottom: 12, + left: 12, + right: 12, + display: 'flex', + justifyContent: 'center', + zIndex: 2 + }, + + // 图片名称标签 + imageNameChip: { + backdropFilter: 'blur(10px)', + bgcolor: 'rgba(255, 255, 255, 0.95)', + fontWeight: 600, + maxWidth: '90%', + boxShadow: 2, + '& .MuiChip-label': { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + } + }, + + // 卡片内容 + cardContent: { + flexGrow: 1, + p: 2.5 + }, + + // 问题文本 + questionText: { + fontWeight: 600, + fontSize: '0.95rem', + lineHeight: 1.5, + mb: 1.5, + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + textOverflow: 'ellipsis' + }, + + // 答案预览 + answerPreview: { + color: 'text.secondary', + fontSize: '0.875rem', + lineHeight: 1.6, + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + overflow: 'hidden', + textOverflow: 'ellipsis', + mb: 2 + }, + + // 元数据信息 + metaInfo: { + display: 'flex', + gap: 1.5, + flexWrap: 'wrap', + mt: 2, + pt: 2, + borderTop: '1px solid', + borderColor: 'divider' + }, + + metaItem: { + display: 'flex', + alignItems: 'center', + gap: 0.5, + fontSize: '0.75rem', + color: 'text.secondary' + }, + + // 分页样式 + pagination: { + display: 'flex', + justifyContent: 'center', + mt: 4 + }, + + // 操作按钮容器 + actionButtonsContainer: { + display: 'flex', + justifyContent: 'flex-end', + gap: 0.5, + mt: 'auto' + }, + + // 操作按钮样式 + actionButton: { + p: 0.5, + borderRadius: 1, + color: 'text.secondary', + '&:hover': { + backgroundColor: 'action.hover', + color: 'primary.main' + } + }, + + // 空状态 + emptyState: { + textAlign: 'center', + py: 12, + px: 3 + }, + + emptyIcon: { + width: 120, + height: 120, + borderRadius: '50%', + bgcolor: 'primary.lighter', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + mx: 'auto', + mb: 3 + }, + + emptyTitle: { + fontWeight: 600, + mb: 1 + }, + + emptyDescription: { + color: 'text.secondary', + mb: 4 + } +}; diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/DatasetDialog.js b/easy-dataset-main/app/projects/[projectId]/images/components/DatasetDialog.js new file mode 100644 index 0000000..73c38bb --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/DatasetDialog.js @@ -0,0 +1,135 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + CircularProgress, + Alert, + Box, + Typography +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; +import { toast } from 'sonner'; +import axios from 'axios'; + +export default function DatasetDialog({ open, projectId, image, onClose, onSuccess }) { + const { t, i18n } = useTranslation(); + const selectedModel = useAtomValue(selectedModelInfoAtom); + const [question, setQuestion] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + if (open) { + setQuestion(''); + setError(''); + } + }, [open]); + + const handleGenerate = async () => { + if (!selectedModel) { + setError(t('images.selectModelFirst')); + return; + } + + if (selectedModel.type !== 'vision') { + setError(t('images.visionModelRequired')); + return; + } + + if (!question.trim()) { + setError(t('images.questionRequired')); + return; + } + + try { + setLoading(true); + setError(''); + + await axios.post(`/api/projects/${projectId}/images/datasets`, { + imageName: image.imageName, + question: { question: question.trim() }, + model: selectedModel, + language: i18n.language + }); + + toast.success(t('images.datasetGenerated')); + onSuccess?.(); + onClose(); + } catch (err) { + console.error('Failed to generate dataset:', err); + setError(err.response?.data?.error || t('images.generateFailed')); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + if (!loading) { + onClose(); + } + }; + + return ( + + {t('images.generateDataset')} + + {error && ( + + {error} + + )} + + {image && ( + + + {t('images.imageName')} + + + {image.imageName} + + + )} + + setQuestion(e.target.value)} + placeholder={t('images.questionPlaceholder')} + disabled={loading} + /> + + {selectedModel && ( + + + {t('images.currentModel')}: {selectedModel.modelName} + + + )} + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/ImageFilters.js b/easy-dataset-main/app/projects/[projectId]/images/components/ImageFilters.js new file mode 100644 index 0000000..8c16da8 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/ImageFilters.js @@ -0,0 +1,111 @@ +'use client'; + +import { + Box, + TextField, + Select, + MenuItem, + FormControl, + InputLabel, + InputAdornment, + Card, + CardContent, + ToggleButtonGroup, + ToggleButton +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import GridViewIcon from '@mui/icons-material/GridView'; +import ViewListIcon from '@mui/icons-material/ViewList'; +import { useTranslation } from 'react-i18next'; +import { useDebounce } from '@/hooks/useDebounce'; +import { useEffect, useState } from 'react'; +import { imageStyles } from '../styles/imageStyles'; + +export default function ImageFilters({ + imageName, + onImageNameChange, + hasQuestions, + onHasQuestionsChange, + hasDatasets, + onHasDatasetsChange, + viewMode = 'grid', + onViewModeChange +}) { + const { t } = useTranslation(); + const [localImageName, setLocalImageName] = useState(imageName); + const debouncedImageName = useDebounce(localImageName, 500); + + useEffect(() => { + onImageNameChange(debouncedImageName); + }, [debouncedImageName]); + + return ( + + + + {/* 搜索框 */} + setLocalImageName(e.target.value)} + size="small" + sx={imageStyles.searchField} + InputProps={{ + startAdornment: ( + + + + ) + }} + /> + + {/* 问题状态筛选 */} + + {t('images.hasQuestions', { defaultValue: '问题状态' })} + + + + {/* 数据集状态筛选 */} + + {t('images.hasDatasets', { defaultValue: '数据集状态' })} + + + + {/* 视图切换 */} + {onViewModeChange && ( + newMode && onViewModeChange(newMode)} + size="small" + sx={imageStyles.viewToggle} + > + + + + + + + + )} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/ImageGrid.js b/easy-dataset-main/app/projects/[projectId]/images/components/ImageGrid.js new file mode 100644 index 0000000..c7eda98 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/ImageGrid.js @@ -0,0 +1,193 @@ +'use client'; + +import { useState } from 'react'; +import { + Grid, + Card, + CardMedia, + CardContent, + CardActions, + Typography, + Chip, + Box, + Pagination, + Tooltip, + Dialog, + DialogContent, + IconButton, + Button +} from '@mui/material'; +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import DatasetIcon from '@mui/icons-material/Dataset'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditNoteIcon from '@mui/icons-material/EditNote'; +import PhotoLibraryIcon from '@mui/icons-material/PhotoLibrary'; +import { useTranslation } from 'react-i18next'; +import { imageStyles } from '../styles/imageStyles'; + +export default function ImageGrid({ + images, + total, + page, + pageSize, + onPageChange, + onGenerateQuestions, + onGenerateDataset, + onDelete, + onAnnotate +}) { + const { t } = useTranslation(); + const [previewImage, setPreviewImage] = useState(null); + + if (!images || images.length === 0) { + return ( + + + + + + {t('images.noImages', { defaultValue: '还没有图片' })} + + + {t('images.noImagesDescription', { defaultValue: '开始导入图片,创建您的第一个图片数据集' })} + + + ); + } + + return ( + <> + + {images.map(image => ( + + + {/* 图片区域 */} + + setPreviewImage(image)} + /> + + {/* 悬停遮罩 */} + + + {/* 状态标签 - 悬浮在图片右上角 */} + + 0 ? 'primary' : 'default'} + sx={imageStyles.statusChip} + /> + 0 ? 'success' : 'default'} + sx={imageStyles.statusChip} + /> + + + {/* 文件名标签 - 悬浮在图片底部 */} + + + + + + + + {/* 操作按钮区域 */} + + + + onGenerateQuestions(image)} sx={imageStyles.actionIconButton}> + + + + + onGenerateDataset(image)} sx={imageStyles.actionIconButton}> + + + + + onDelete(image.id)} + sx={imageStyles.actionIconButton} + > + + + + + + + ))} + + + {total > pageSize && ( + + onPageChange(newPage)} + color="primary" + size="large" + showFirstButton + showLastButton + /> + + )} + + {/* 图片预览对话框 */} + setPreviewImage(null)} + maxWidth="lg" + fullWidth + PaperProps={{ + sx: { + bgcolor: 'transparent', + boxShadow: 'none', + overflow: 'hidden' + } + }} + > + + {previewImage && ( + + {previewImage.imageName} + + {previewImage.imageName} + + + )} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/ImageList.js b/easy-dataset-main/app/projects/[projectId]/images/components/ImageList.js new file mode 100644 index 0000000..668fc58 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/ImageList.js @@ -0,0 +1,315 @@ +'use client'; + +import { useState } from 'react'; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Chip, + Box, + Pagination, + Tooltip, + IconButton, + Avatar, + Dialog, + DialogContent, + Typography, + Button, + Checkbox +} from '@mui/material'; +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import DatasetIcon from '@mui/icons-material/Dataset'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditNoteIcon from '@mui/icons-material/EditNote'; +import PhotoLibraryIcon from '@mui/icons-material/PhotoLibrary'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import { useTranslation } from 'react-i18next'; +import { imageStyles } from '../styles/imageStyles'; + +export default function ImageList({ + images, + total, + page, + pageSize, + onPageChange, + onGenerateQuestions, + onGenerateDataset, + onDelete, + onAnnotate, + selectedIds = [], + onSelectionChange +}) { + const { t } = useTranslation(); + const [previewImage, setPreviewImage] = useState(null); + + // 处理全选/取消全选 + const handleSelectAll = event => { + if (event.target.checked) { + const allIds = images.map(img => img.id); + onSelectionChange?.(allIds); + } else { + onSelectionChange?.([]); + } + }; + + // 处理单个选择 + const handleSelectOne = (imageId, checked) => { + if (checked) { + onSelectionChange?.([...selectedIds, imageId]); + } else { + onSelectionChange?.(selectedIds.filter(id => id !== imageId)); + } + }; + + // 判断是否全选 + const isAllSelected = images.length > 0 && selectedIds.length === images.length; + const isSomeSelected = selectedIds.length > 0 && selectedIds.length < images.length; + + // 格式化日期 + const formatDate = dateString => { + if (!dateString) return '-'; + const date = new Date(dateString); + return date.toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); + }; + + // 格式化文件大小 + const formatSize = bytes => { + if (!bytes) return '-'; + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(2)} MB`; + }; + + if (!images || images.length === 0) { + return ( + + + + + + {t('images.noImages', { defaultValue: '还没有图片' })} + + + {t('images.noImagesDescription', { defaultValue: '开始导入图片,创建您的第一个图片数据集' })} + + + ); + } + + return ( + <> + + + + + + + + {t('images.preview', { defaultValue: '预览' })} + {t('images.fileName', { defaultValue: '文件名' })} + {t('images.size', { defaultValue: '大小' })} + {t('images.dimensions', { defaultValue: '尺寸' })} + {t('images.questionCount', { defaultValue: '问题数' })} + {t('images.datasetCount', { defaultValue: '数据集数' })} + {t('images.uploadTime', { defaultValue: '上传时间' })} + + {t('common.actions', { defaultValue: '操作' })} + + + + + {images.map(image => ( + + {/* 复选框 */} + + handleSelectOne(image.id, e.target.checked)} + /> + + + {/* 预览缩略图 */} + + setPreviewImage(image)} + /> + + + {/* 文件名 */} + + + + {image.imageName} + + + + + {/* 文件大小 */} + + + {formatSize(image.size)} + + + + {/* 尺寸 */} + + {image.width && image.height ? ( + + {image.width} × {image.height} + + ) : ( + + - + + )} + + + {/* 问题数 */} + + 0 ? 'primary' : 'default'} + variant="outlined" + /> + + + {/* 数据集数 */} + + 0 ? 'success' : 'default'} + variant="outlined" + /> + + + {/* 上传时间 */} + + + {formatDate(image.createAt)} + + + + {/* 操作按钮 */} + + + + setPreviewImage(image)}> + + + + + onAnnotate(image)}> + + + + + onGenerateQuestions(image)}> + + + + + onGenerateDataset(image)}> + + + + + onDelete(image.id)}> + + + + + + + ))} + +
+
+ + {/* 分页 */} + {total > pageSize && ( + + onPageChange(newPage)} + color="primary" + size="large" + showFirstButton + showLastButton + /> + + )} + + {/* 图片预览对话框 */} + setPreviewImage(null)} + maxWidth="lg" + fullWidth + PaperProps={{ + sx: { + bgcolor: 'transparent', + boxShadow: 'none', + overflow: 'hidden' + } + }} + > + + {previewImage && ( + + {previewImage.imageName} + + {previewImage.imageName} + + + )} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/ImportDialog.js b/easy-dataset-main/app/projects/[projectId]/images/components/ImportDialog.js new file mode 100644 index 0000000..90b5eea --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/ImportDialog.js @@ -0,0 +1,416 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + List, + ListItem, + ListItemText, + IconButton, + CircularProgress, + Alert, + TextField, + Tabs, + Tab, + Paper, + Chip, + Card +} from '@mui/material'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; +import UploadFileIcon from '@mui/icons-material/UploadFile'; +import FolderZipIcon from '@mui/icons-material/FolderZip'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import axios from 'axios'; + +export default function ImportDialog({ open, projectId, onClose, onSuccess }) { + const { t } = useTranslation(); + const [mode, setMode] = useState(0); // 0: 目录导入, 1: PDF 导入, 2: 压缩包导入 + const [directories, setDirectories] = useState([]); + const [loading, setLoading] = useState(false); + const [inputPath, setInputPath] = useState(''); + const [selectedPdf, setSelectedPdf] = useState(null); + const [selectedZip, setSelectedZip] = useState(null); + + const handleAddDirectory = () => { + if (inputPath.trim() && !directories.includes(inputPath.trim())) { + setDirectories([...directories, inputPath.trim()]); + setInputPath(''); + } + }; + + const handleRemoveDirectory = index => { + setDirectories(directories.filter((_, i) => i !== index)); + }; + + const handleImport = async () => { + if (directories.length === 0) { + toast.error(t('images.selectAtLeastOne')); + return; + } + + try { + setLoading(true); + const response = await axios.post(`/api/projects/${projectId}/images`, { + directories + }); + + toast.success(t('images.importSuccess', { count: response.data.count })); + setDirectories([]); + onSuccess?.(); + } catch (error) { + console.error('Failed to import images:', error); + toast.error(error.response?.data?.error || t('images.importFailed')); + } finally { + setLoading(false); + } + }; + + const handlePdfSelect = event => { + const file = event.target.files?.[0]; + if (file && file.type === 'application/pdf') { + setSelectedPdf(file); + } else { + toast.error(t('images.invalidPdfFile', { defaultValue: '请选择有效的 PDF 文件' })); + } + }; + + const handlePdfImport = async () => { + if (!selectedPdf) { + toast.error(t('images.selectPdfFile', { defaultValue: '请选择 PDF 文件' })); + return; + } + + try { + setLoading(true); + const formData = new FormData(); + formData.append('file', selectedPdf); + + // 调用 PDF 转换 API + const response = await axios.post(`/api/projects/${projectId}/images/pdf-convert`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + toast.success( + t('images.pdfImportSuccess', { + defaultValue: `成功从 PDF "${response.data.pdfName}" 导入 ${response.data.count} 张图片`, + count: response.data.count, + name: response.data.pdfName + }) + ); + setSelectedPdf(null); + onSuccess?.(); + } catch (error) { + console.error('Failed to import PDF:', error); + toast.error(error.response?.data?.error || t('images.pdfImportFailed', { defaultValue: 'PDF 导入失败' })); + } finally { + setLoading(false); + } + }; + + const handleZipSelect = event => { + const file = event.target.files?.[0]; + if (file && file.name.toLowerCase().endsWith('.zip')) { + setSelectedZip(file); + } else { + toast.error(t('images.invalidZipFile', { defaultValue: '请选择有效的 ZIP 文件' })); + } + }; + + const handleZipImport = async () => { + if (!selectedZip) { + toast.error(t('images.selectZipFile', { defaultValue: '请选择 ZIP 文件' })); + return; + } + + try { + setLoading(true); + const formData = new FormData(); + formData.append('file', selectedZip); + + // 调用压缩包导入 API + const response = await axios.post(`/api/projects/${projectId}/images/zip-import`, formData, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }); + + toast.success( + t('images.zipImportSuccess', { + defaultValue: `成功从压缩包 "${response.data.zipName}" 导入 ${response.data.count} 张图片`, + count: response.data.count, + name: response.data.zipName + }) + ); + setSelectedZip(null); + onSuccess?.(); + } catch (error) { + console.error('Failed to import ZIP:', error); + toast.error(error.response?.data?.error || t('images.zipImportFailed', { defaultValue: '压缩包导入失败' })); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + if (!loading) { + setDirectories([]); + setSelectedPdf(null); + setSelectedZip(null); + setMode(0); + onClose(); + } + }; + + return ( + + {t('images.importImages')} + + setMode(newValue)} + sx={{ mb: 3, borderBottom: 1, borderColor: 'divider' }} + > + } + iconPosition="start" + /> + } + iconPosition="start" + /> + } + iconPosition="start" + /> + + + {mode === 0 ? ( + <> + + {t('images.importTip')} + + + + setInputPath(e.target.value)} + onKeyPress={e => { + if (e.key === 'Enter') { + handleAddDirectory(); + } + }} + disabled={loading} + sx={{ + '& .MuiOutlinedInput-root': { + borderRadius: 2 + } + }} + /> + + + + {directories.length > 0 && ( + + + + + {t('images.selectedDirectories')} ({directories.length}) + + + + {directories.map((dir, index) => ( + handleRemoveDirectory(index)} + disabled={loading} + icon={} + sx={{ + borderRadius: 1.5, + fontWeight: 500, + maxWidth: '100%', + '& .MuiChip-label': { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + } + }} + /> + ))} + + + )} + + ) : mode === 1 ? ( + <> + + {t('images.pdfImportTip', { defaultValue: '选择 PDF 文件,系统会自动将其转换为图片并导入' })} + + + document.getElementById('pdf-file-input').click()} + > + + + + {selectedPdf ? selectedPdf.name : t('images.clickToSelectPdf', { defaultValue: '点击选择 PDF 文件' })} + + + {t('images.supportedFormat', { defaultValue: '支持格式:PDF' })} + + {selectedPdf && ( + + {t('images.fileSize', { defaultValue: '文件大小' })}: {(selectedPdf.size / 1024 / 1024).toFixed(2)} MB + + )} + + + ) : ( + <> + + {t('images.zipImportTip', { defaultValue: '选择 ZIP 压缩包文件,系统会自动解压并导入其中的图片' })} + + + document.getElementById('zip-file-input').click()} + > + + + + {selectedZip ? selectedZip.name : t('images.clickToSelectZip', { defaultValue: '点击选择 ZIP 文件' })} + + + {t('images.supportedZipFormat', { defaultValue: '支持格式:ZIP' })} + + {selectedZip && ( + + {t('images.fileSize', { defaultValue: '文件大小' })}: {(selectedZip.size / 1024 / 1024).toFixed(2)} MB + + )} + + + )} + + + + {mode === 0 ? ( + + ) : mode === 1 ? ( + + ) : ( + + )} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/QuestionDialog.js b/easy-dataset-main/app/projects/[projectId]/images/components/QuestionDialog.js new file mode 100644 index 0000000..9ff3229 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/QuestionDialog.js @@ -0,0 +1,135 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + CircularProgress, + Alert, + Box, + Typography +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; +import { toast } from 'sonner'; +import axios from 'axios'; + +export default function QuestionDialog({ open, projectId, image, onClose, onSuccess }) { + const { t, i18n } = useTranslation(); + const selectedModel = useAtomValue(selectedModelInfoAtom); + const [count, setCount] = useState(3); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + if (open) { + setCount(3); + setError(''); + } + }, [open]); + + const handleGenerate = async () => { + if (!selectedModel) { + setError(t('images.selectModelFirst')); + return; + } + + if (selectedModel.type !== 'vision') { + setError(t('images.visionModelRequired')); + return; + } + + if (count < 1 || count > 10) { + setError(t('images.countRange')); + return; + } + + try { + setLoading(true); + setError(''); + + const response = await axios.post(`/api/projects/${projectId}/images/questions`, { + imageName: image.imageName, + count, + model: selectedModel, + language: i18n.language + }); + + toast.success(t('images.questionsGenerated', { count: response.data.questions.length })); + onSuccess?.(); + onClose(); + } catch (err) { + console.error('Failed to generate questions:', err); + setError(err.response?.data?.error || t('images.generateFailed')); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + if (!loading) { + onClose(); + } + }; + + return ( + + {t('images.generateQuestions')} + + {error && ( + + {error} + + )} + + {image && ( + + + {t('images.imageName')} + + + {image.imageName} + + + )} + + setCount(parseInt(e.target.value) || 1)} + inputProps={{ min: 1, max: 10 }} + helperText={t('images.questionCountHelp')} + disabled={loading} + /> + + {selectedModel && ( + + + {t('images.currentModel')}: {selectedModel.modelName} + + + )} + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AIGenerateButton.js b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AIGenerateButton.js new file mode 100644 index 0000000..b33a321 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AIGenerateButton.js @@ -0,0 +1,97 @@ +'use client'; + +import { Button, CircularProgress } from '@mui/material'; +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useAtomValue } from 'jotai/index'; +import { selectedModelInfoAtom } from '@/lib/store'; + +/** + * AI 生成答案按钮组件 + * @param {string} projectId - 项目ID + * @param {string} imageName - 图片名称 + * @param {string} question - 问题内容 + * @param {function} onSuccess - 生成成功的回调,接收生成的答案 + * @param {boolean} previewOnly - 是否只预览(不保存数据集),默认 true + * @param {object} sx - 自定义样式 + */ +export default function AIGenerateButton({ + projectId, + imageName, + question, + onSuccess, + previewOnly = true, + sx = {}, + answerType +}) { + const { t, i18n } = useTranslation(); + const [loading, setLoading] = useState(false); + const model = useAtomValue(selectedModelInfoAtom); + + const handleGenerate = async () => { + if (!projectId || !imageName || !question) { + toast.error(t('images.missingParameters', { defaultValue: '缺少必要参数' })); + return; + } + + if (model.type !== 'vision') { + toast.error(t('images.visionModelRequired', { defaultValue: '请选择支持视觉的模型' })); + return; + } + + setLoading(true); + try { + const response = await axios.post(`/api/projects/${projectId}/images/datasets`, { + imageName, + question, + model, + language: i18n.language, + previewOnly + }); + + if (response.data.success && response.data.answer) { + let data = response.data.answer; + if (answerType === 'label') { + try { + data = JSON.parse(response.data.answer); + } catch {} + } + onSuccess(data); + toast.success(t('images.aiGenerateSuccess', { defaultValue: 'AI 生成成功' })); + } + } catch (error) { + console.error('AI 生成失败:', error); + const errorMsg = error.response?.data?.error || t('images.aiGenerateFailed', { defaultValue: 'AI 生成失败' }); + toast.error(errorMsg); + } finally { + setLoading(false); + } + }; + + return ( + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AnnotationDialog.js b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AnnotationDialog.js new file mode 100644 index 0000000..6c6a6c7 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AnnotationDialog.js @@ -0,0 +1,268 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + Chip, + CircularProgress +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import Image from 'next/image'; +import QuestionSelector from './QuestionSelector'; +import AnswerInput from './AnswerInput'; + +export default function AnnotationDialog({ + open, + onClose, + image, + templates, + selectedTemplate, + onTemplateChange, + answer, + onAnswerChange, + onSave, + onSaveAndContinue, + saving, + loading, + onOpenCreateQuestion, + onOpenCreateTemplate +}) { + const { t } = useTranslation(); + + if (!image) return null; + + return ( + + + + + {t('images.annotateImage', { defaultValue: '标注图片' })} + + + {image && ( + + )} + + + + + + {/* 图片预览区域 */} + + {/* 图片预览 */} + + {image && ( + <> + + {image.base64 ? ( + {image.imageName} + ) : ( + + + {t('images.imageLoadError', { defaultValue: '图片加载失败' })} + + + )} + + + {/* 图片信息卡片 */} + + + {image.imageName} + + + {image.width && image.height && ( + + )} + {image.size && ( + + )} + {image.format && } + + + {t('images.annotatedCount', { defaultValue: '已标注' })}: {image.datasetCount || 0}{' '} + {t('images.questions', { defaultValue: '个问题' })} + + + + )} + + + {/* 标注区域 */} + + {/* 问题选择器 */} + + {loading ? ( + + + + ) : ( + + )} + + + {/* 答案输入区域 */} + {selectedTemplate && ( + + + + )} + + + + + + {/* 左侧:创建按钮 */} + + + + + + {/* 右侧:操作按钮 */} + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AnswerInput.js b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AnswerInput.js new file mode 100644 index 0000000..d96f1cf --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/AnswerInput.js @@ -0,0 +1,437 @@ +'use client'; + +import { Box, Typography, TextField, Chip, Button, Paper } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import AddIcon from '@mui/icons-material/Add'; +import AIGenerateButton from './AIGenerateButton'; + +export default function AnswerInput({ + answerType, + answer, + onAnswerChange, + labels, + customFormat, + projectId, + imageName, + question +}) { + const { t, i18n } = useTranslation(); + const [newLabel, setNewLabel] = useState(''); + const [jsonError, setJsonError] = useState(''); + + // 文字类型输入 + if (answerType === 'text') { + return ( + + + + {t('images.answer', { defaultValue: '文本答案' })} * + + + + onAnswerChange(e.target.value)} + placeholder={t('images.answerPlaceholder', { defaultValue: '请输入答案...' })} + sx={{ + '& .MuiOutlinedInput-root': { + borderRadius: 3, + backgroundColor: 'background.paper', + '& fieldset': { + borderWidth: 2, + borderColor: 'divider' + }, + '&:hover fieldset': { + borderColor: 'primary.main' + }, + '&.Mui-focused fieldset': { + borderColor: 'primary.main', + borderWidth: 2 + } + }, + '& textarea': { + fontSize: '14px', + lineHeight: 1.6 + } + }} + /> + + ); + } + + // 标签类型输入 - 提前解析 labels,避免条件中的 hooks 问题 + if (answerType === 'label') { + const selectedLabels = Array.isArray(answer) ? answer : []; + + // 解析 labels(可能是 JSON 字符串或数组) + let labelOptions = []; + if (typeof labels === 'string' && labels) { + try { + labelOptions = JSON.parse(labels); + } catch (e) { + labelOptions = []; + } + } else if (Array.isArray(labels)) { + labelOptions = labels; + } + + if (!labelOptions.includes('其他') && !labelOptions.includes('other')) { + labelOptions.push(i18n.language === 'en' ? 'other' : '其他'); + } + + const handleToggleLabel = label => { + if (selectedLabels.includes(label)) { + onAnswerChange(selectedLabels.filter(l => l !== label)); + } else { + let newLabels = [...selectedLabels, label]; + onAnswerChange(newLabels); + } + }; + + const handleAddNewLabel = () => { + if (newLabel.trim() && !labelOptions.includes(newLabel.trim())) { + handleToggleLabel(newLabel.trim()); + setNewLabel(''); + } + }; + + return ( + + + + {t('images.selectLabels', { defaultValue: '标签选择' })} * + + + + + {/* 可选标签 */} + + + {t('images.availableLabels', { defaultValue: '可选标签' })} + + + {labelOptions && labelOptions.length > 0 ? ( + labelOptions.map(label => ( + handleToggleLabel(label)} + color={selectedLabels.includes(label) ? 'primary' : 'default'} + variant={selectedLabels.includes(label) ? 'filled' : 'outlined'} + sx={{ + borderRadius: 2, + fontWeight: 500, + fontSize: '0.875rem', + height: 36, + cursor: 'pointer', + transition: 'all 0.2s ease', + '&:hover': { + transform: 'translateY(-1px)', + boxShadow: 2 + } + }} + /> + )) + ) : ( + + {t('images.noLabelsAvailable', { defaultValue: '暂无可选标签' })} + + )} + + + + {/* 添加新标签 */} + {/* + setNewLabel(e.target.value)} + placeholder={t('images.addNewLabel', { defaultValue: '添加新标签...' })} + onKeyPress={e => { + if (e.key === 'Enter') { + handleAddNewLabel(); + } + }} + sx={{ + flex: 1, + '& .MuiOutlinedInput-root': { + borderRadius: 2, + backgroundColor: 'background.paper', + '& fieldset': { + borderWidth: 2 + }, + '&:hover fieldset': { + borderColor: 'primary.main' + } + } + }} + /> + + */} + + {/* 已选择标签 */} + {/* {selectedLabels.length > 0 && ( + + + {t('images.selectedLabels', { defaultValue: '已选择' })} ({selectedLabels.length}) + + + + {selectedLabels.map(label => ( + handleToggleLabel(label)} + color="primary" + sx={{ + borderRadius: 2, + fontWeight: 500, + fontSize: '0.875rem', + height: 36, + '& .MuiChip-deleteIcon': { + fontSize: '18px', + '&:hover': { + color: 'error.main' + } + } + }} + /> + ))} + + + + )} */} + + ); + } + + // 自定义格式输入 + if (answerType === 'custom_format') { + const handleJsonChange = value => { + onAnswerChange(value); + // 验证 JSON 格式 + if (value.trim()) { + try { + JSON.parse(value); + setJsonError(''); + } catch (e) { + setJsonError(t('images.invalidJsonFormat', { defaultValue: 'JSON 格式不正确' })); + } + } else { + setJsonError(''); + } + }; + + const handleUseTemplate = () => { + if (customFormat) { + try { + let templateJson; + if (typeof customFormat === 'string') { + templateJson = JSON.parse(customFormat); + } else { + templateJson = customFormat; + } + const formatted = JSON.stringify(templateJson, null, 2); + onAnswerChange(formatted); + setJsonError(''); + } catch (e) { + onAnswerChange('{}'); + } + } + }; + + if (answer && typeof answer === 'object') { + answer = JSON.stringify(answer, null, 2); + } + + return ( + + + + {t('images.customFormatAnswer', { defaultValue: '自定义格式答案' })} * + + + + {customFormat && ( + + )} + {/* */} + + + + {/* 显示格式要求 */} + {customFormat && ( + + + {t('images.formatRequirement', { defaultValue: '格式要求' })} + + +
+                {typeof customFormat === 'string' ? customFormat : JSON.stringify(customFormat, null, 2)}
+              
+
+
+ )} + + {/* JSON 输入框 */} + handleJsonChange(e.target.value)} + placeholder={t('images.customFormatPlaceholder', { defaultValue: '请输入符合格式的 JSON...' })} + error={!!jsonError} + sx={{ + '& .MuiOutlinedInput-root': { + borderRadius: 3, + backgroundColor: 'background.paper', + '& fieldset': { + borderWidth: 2 + }, + '&:hover fieldset': { + borderColor: 'primary.main' + }, + '&.Mui-focused fieldset': { + borderColor: 'primary.main', + borderWidth: 2 + }, + '&.Mui-error fieldset': { + borderColor: 'error.main', + borderWidth: 2 + } + }, + '& textarea': { + fontFamily: 'Monaco, Consolas, "Courier New", monospace', + fontSize: '13px', + lineHeight: 1.5 + }, + '& .MuiFormHelperText-root': { + fontSize: '0.875rem', + fontWeight: 500 + } + }} + /> +
+ ); + } + + return null; +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/components/annotation/QuestionSelector.js b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/QuestionSelector.js new file mode 100644 index 0000000..7246d94 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/components/annotation/QuestionSelector.js @@ -0,0 +1,200 @@ +'use client'; + +import { Autocomplete, TextField, Box, Typography, Chip, Button, Dialog } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; + +export default function QuestionSelector({ + templates, + selectedTemplate, + onTemplateChange, + answeredQuestions = [], + unansweredQuestions = [], + onOpenCreateQuestion, + onOpenCreateTemplate +}) { + const { t } = useTranslation(); + const [showNoQuestionsMessage, setShowNoQuestionsMessage] = useState(false); + + // 构建未完成标注的问题选项(用于下拉框) + const dropdownOptions = unansweredQuestions.map(q => ({ + ...q, + isUnanswered: true + })); + + const getAnswerTypeLabel = answerType => { + switch (answerType) { + case 'text': + return t('images.answerTypeText', { defaultValue: '文字' }); + case 'label': + return t('images.answerTypeLabel', { defaultValue: '标签' }); + case 'custom_format': + return t('images.answerTypeCustomFormat', { defaultValue: '自定义格式' }); + default: + return answerType; + } + }; + + // 判断是否有待标注问题 + const hasUnansweredQuestions = unansweredQuestions.length > 0; + const hasAnsweredQuestions = answeredQuestions.length > 0; + const hasAnyQuestions = hasUnansweredQuestions || hasAnsweredQuestions; + + return ( + + {/* 已标注问题区域 - 优化显示为一行,添加最大高度 */} + {answeredQuestions.length > 0 && ( + + + {t('images.answeredQuestions', { defaultValue: '已标注问题' })} ({answeredQuestions.length}) + + + {answeredQuestions.map(question => ( + + ))} + + + )} + + {/* 问题选择下拉框 */} + + + {t('images.selectNewQuestion', { defaultValue: '选择新问题' })} + + + {!hasUnansweredQuestions ? ( + // 没有待标注问题的提示 + + {hasAnsweredQuestions ? ( + + {t('images.allQuestionsAnnotated', { defaultValue: '当前图片所有问题已标注完成' })} + + ) : ( + + {t('images.noQuestionsAssociated', { defaultValue: '当前图片未关联任何问题' })} + + )} + + ) : ( + // 有待标注问题时显示下拉框 + { + if (newValue) { + onTemplateChange(newValue); + } + }} + getOptionLabel={option => option.question || ''} + renderOption={(props, option) => ( + + + + {option.question} + + + + + + + + )} + renderInput={params => ( + + )} + isOptionEqualToValue={(option, value) => option.id === value.id} + /> + )} + + {selectedTemplate && selectedTemplate.description && ( + + {selectedTemplate.description} + + )} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/hooks/useAnnotation.js b/easy-dataset-main/app/projects/[projectId]/images/hooks/useAnnotation.js new file mode 100644 index 0000000..3637ef6 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/hooks/useAnnotation.js @@ -0,0 +1,287 @@ +import { useState } from 'react'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; + +// 深度遍历 JSON,将所有值设为空字符串 +function clearJsonValues(obj) { + if (Array.isArray(obj)) { + return obj.map(item => clearJsonValues(item)); + } else if (obj !== null && typeof obj === 'object') { + const cleared = {}; + for (const key in obj) { + cleared[key] = clearJsonValues(obj[key]); + } + return cleared; + } else { + return ''; // 所有基础类型值都变为空字符串 + } +} + +export function useAnnotation(projectId, onSuccess, onFindNextImage) { + const { t } = useTranslation(); + const [open, setOpen] = useState(false); + const [saving, setSaving] = useState(false); + const [loading, setLoading] = useState(false); + const [currentImage, setCurrentImage] = useState(null); + const [selectedTemplate, setSelectedTemplate] = useState(null); + const [answer, setAnswer] = useState(''); + + // 打开标注对话框 + const openAnnotation = async (image, template = null) => { + setLoading(true); + try { + // 获取图片详情,包括已标注和未标注的问题 + const response = await axios.get(`/api/projects/${projectId}/images/${image.id}`); + if (response.data.success) { + const imageDetail = response.data.data; + setCurrentImage(imageDetail); + + // 如果没有指定模板,尝试选择第一个未标注的问题 + if (!template) { + if (imageDetail.unansweredQuestions?.length > 0) { + template = imageDetail.unansweredQuestions[0]; + } + } + + setSelectedTemplate(template); + + // 根据问题类型初始化答案 + let initialAnswer = ''; + if (template?.answerType === 'label') { + initialAnswer = []; + } else if (template?.answerType === 'custom_format' && template?.customFormat) { + // 为自定义格式提供默认值(所有字段值清空) + try { + let templateJson; + if (typeof template.customFormat === 'string') { + // 如果customFormat是字符串,尝试解析为JSON + templateJson = JSON.parse(template.customFormat); + } else { + // 如果customFormat已经是对象,直接使用 + templateJson = template.customFormat; + } + // 深度遍历,将所有字段值清空 + const clearedJson = clearJsonValues(templateJson); + initialAnswer = JSON.stringify(clearedJson, null, 2); + } catch (error) { + // 如枟解析失败,提供一个空的JSON对象 + initialAnswer = '{}'; + } + } + + setAnswer(initialAnswer); + setOpen(true); + } else { + toast.error(t('images.loadImageDetailFailed', { defaultValue: '加载图片详情失败' })); + } + } catch (error) { + console.error('获取图片详情失败:', error); + toast.error(t('images.loadImageDetailFailed', { defaultValue: '加载图片详情失败' })); + } finally { + setLoading(false); + } + }; + + // 关闭对话框 + const closeAnnotation = () => { + setOpen(false); + setCurrentImage(null); + setSelectedTemplate(null); + setAnswer(''); + }; + + // 刷新当前图片的问题列表(创建问题后调用) + const refreshCurrentImage = async () => { + if (!currentImage) return; + + try { + const response = await axios.get(`/api/projects/${projectId}/images/${currentImage.id}`); + if (response.data.success) { + const imageDetail = response.data.data; + // 更新当前图片数据 + setCurrentImage(imageDetail); + return imageDetail; + } + } catch (error) { + console.error('刷新图片详情失败:', error); + } + }; + + // 查找下一个未标注的问题 + const findNextUnansweredQuestion = async () => { + // 重新获取图片详情,获取最新的问题列表 + try { + const response = await axios.get(`/api/projects/${projectId}/images/${currentImage.id}`); + if (response.data.success) { + const imageDetail = response.data.data; + + // 更新当前图片数据 + setCurrentImage(imageDetail); + + // 返回第一个未标注的问题 + if (imageDetail.unansweredQuestions?.length > 0) { + return imageDetail.unansweredQuestions[0]; + } + + return null; + } + } catch (error) { + console.error('获取下一个问题失败:', error); + return null; + } + }; + + // 保存标注 + const saveAnnotation = async (continueNext = false) => { + if (!currentImage) { + toast.error(t('images.noImageSelected', { defaultValue: '未选择图片' })); + return; + } + + if (!selectedTemplate) { + toast.error(t('images.noTemplateSelected', { defaultValue: '请选择问题' })); + return; + } + + // 验证答案 + if (!answer || (Array.isArray(answer) && answer.length === 0)) { + toast.error(t('images.answerRequired', { defaultValue: '请输入答案' })); + return; + } + + // 如果是自定义格式,验证 JSON 格式 + if (selectedTemplate.answerType === 'custom_format') { + try { + JSON.parse(answer); + } catch (e) { + toast.error(t('images.invalidJsonFormat', { defaultValue: 'JSON 格式不正确' })); + return; + } + } + + console.log(999, answer); + setSaving(true); + try { + const response = await axios.post(`/api/projects/${projectId}/images/annotations`, { + imageId: currentImage.id, + imageName: currentImage.imageName, + questionId: selectedTemplate.id, + question: selectedTemplate.question, + templateId: selectedTemplate.id, + answerType: selectedTemplate.answerType, + answer + }); + + if (response.data.success) { + toast.success(t('images.annotationSuccess', { defaultValue: '标注保存成功' })); + + // 触发刷新回调 + if (onSuccess) { + onSuccess(); + } + + if (continueNext) { + // 查找下一个未标注的问题 + const nextQuestion = await findNextUnansweredQuestion(); + + if (nextQuestion) { + // 切换到下一个问题 + setSelectedTemplate(nextQuestion); + + // 根据问题类型初始化答案 + let initialAnswer = ''; + if (nextQuestion.answerType === 'label') { + initialAnswer = []; + } else if (nextQuestion.answerType === 'custom_format' && nextQuestion.customFormat) { + try { + let templateJson; + if (typeof nextQuestion.customFormat === 'string') { + templateJson = JSON.parse(nextQuestion.customFormat); + } else { + templateJson = nextQuestion.customFormat; + } + const clearedJson = clearJsonValues(templateJson); + initialAnswer = JSON.stringify(clearedJson, null, 2); + } catch (error) { + initialAnswer = '{}'; + } + } + setAnswer(initialAnswer); + } else { + // 没有更多未标注的问题了,尝试查找下一个有未标注问题的图片 + if (onFindNextImage) { + const nextImage = await onFindNextImage(); + if (nextImage) { + // 打开下一个图片的标注 + await openAnnotation(nextImage); + } else { + // 没有更多图片了 + toast.info(t('images.allImagesAnnotated', { defaultValue: '所有图片的问题都已标注完成' })); + closeAnnotation(); + } + } else { + toast.info(t('images.allQuestionsAnnotated', { defaultValue: '当前图片所有问题已标注完成' })); + closeAnnotation(); + } + } + } else { + closeAnnotation(); + } + } + } catch (error) { + console.error('保存标注失败:', error); + const errorMsg = error.response?.data?.error || t('images.annotationFailed', { defaultValue: '保存标注失败' }); + toast.error(errorMsg); + } finally { + setSaving(false); + } + }; + + // 处理模板变更 + const handleTemplateChange = template => { + setSelectedTemplate(template); + + // 根据新模板类型初始化答案 + let initialAnswer = ''; + if (template?.answerType === 'label') { + initialAnswer = []; + } else if (template?.answerType === 'custom_format' && template?.customFormat) { + // 为自定义格式提供默认值(所有字段值清空) + try { + let templateJson; + if (typeof template.customFormat === 'string') { + // 如果customFormat是字符串,尝试解析为JSON + templateJson = JSON.parse(template.customFormat); + } else { + // 如果customFormat已经是对象,直接使用 + templateJson = template.customFormat; + } + // 深度遍历,将所有字段值清空 + const clearedJson = clearJsonValues(templateJson); + initialAnswer = JSON.stringify(clearedJson, null, 2); + } catch (error) { + // 如枟解析失败,提供一个空的JSON对象 + initialAnswer = '{}'; + } + } + + setAnswer(initialAnswer); + }; + + return { + open, + saving, + loading, + currentImage, + selectedTemplate, + answer, + setSelectedTemplate, + setAnswer, + handleTemplateChange, + openAnnotation, + closeAnnotation, + saveAnnotation, + refreshCurrentImage + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/page.js b/easy-dataset-main/app/projects/[projectId]/images/page.js new file mode 100644 index 0000000..0d663b5 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/page.js @@ -0,0 +1,486 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useParams, useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { + Container, + Box, + Typography, + Button, + CircularProgress, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField +} from '@mui/material'; +import AddPhotoAlternateIcon from '@mui/icons-material/AddPhotoAlternate'; +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { imageStyles } from './styles/imageStyles'; +import { toast } from 'sonner'; +import axios from 'axios'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; + +import ImageFilters from './components/ImageFilters'; +import ImageGrid from './components/ImageGrid'; +import ImageList from './components/ImageList'; +import ImportDialog from './components/ImportDialog'; +import QuestionDialog from './components/QuestionDialog'; +import DatasetDialog from './components/DatasetDialog'; +import AnnotationDialog from './components/annotation/AnnotationDialog'; +import { useQuestionTemplates } from '../questions/hooks/useQuestionTemplates'; +import { useAnnotation } from './hooks/useAnnotation'; +import { useQuestionEdit } from '../questions/hooks/useQuestionEdit'; +import QuestionEditDialog from '../questions/components/QuestionEditDialog'; +import TemplateFormDialog from '../questions/components/template/TemplateFormDialog'; + +export default function ImagesPage() { + const { projectId } = useParams(); + const router = useRouter(); + const { t, i18n } = useTranslation(); + const selectedModel = useAtomValue(selectedModelInfoAtom); + + const [loading, setLoading] = useState(false); + const [images, setImages] = useState([]); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [pageSize] = useState(8); + + // 筛选条件 + const [imageName, setImageName] = useState(''); + const [hasQuestions, setHasQuestions] = useState('all'); + const [hasDatasets, setHasDatasets] = useState('all'); + + // 视图模式 + const [viewMode, setViewMode] = useState('grid'); + + // 选中状态(仅列表视图使用) + const [selectedIds, setSelectedIds] = useState([]); + + // 对话框状态 + const [importDialogOpen, setImportDialogOpen] = useState(false); + const [questionDialogOpen, setQuestionDialogOpen] = useState(false); + const [datasetDialogOpen, setDatasetDialogOpen] = useState(false); + const [selectedImage, setSelectedImage] = useState(null); + const [autoGenerateDialogOpen, setAutoGenerateDialogOpen] = useState(false); + const [questionCount, setQuestionCount] = useState(3); + + // 问题模板和标注功能 (只获取图像类型的模板) + const { templates, createTemplate } = useQuestionTemplates(projectId, 'image'); + + // 问题编辑 Hook + const { editDialogOpen, editMode, editingQuestion, handleOpenCreateDialog, handleCloseDialog, handleSubmitQuestion } = + useQuestionEdit(projectId, async () => { + fetchImages(); + if (annotationOpen && currentImage) { + await refreshCurrentImage(); + } + toast.success(t('questions.operationSuccess')); + }); + + // 模板管理状态 + const [templateDialogOpen, setTemplateDialogOpen] = useState(false); + + // 获取图片列表 + const fetchImages = async () => { + try { + setLoading(true); + const params = new URLSearchParams({ + page: page.toString(), + pageSize: pageSize.toString() + }); + + if (imageName) params.append('imageName', imageName); + if (hasQuestions !== 'all') params.append('hasQuestions', hasQuestions); + if (hasDatasets !== 'all') params.append('hasDatasets', hasDatasets); + + const response = await axios.get(`/api/projects/${projectId}/images?${params.toString()}`); + setImages(response.data.data); + setTotal(response.data.total); + } catch (error) { + console.error('Failed to fetch images:', error); + toast.error(t('common.fetchError')); + } finally { + setLoading(false); + } + }; + + // 查找下一个有未标注问题的图片 + const handleFindNextImage = async () => { + try { + const response = await axios.get(`/api/projects/${projectId}/images/next-unanswered`); + return response.data.data || null; + } catch (error) { + console.error('查找下一个图片失败:', error); + return null; + } + }; + + const { + open: annotationOpen, + saving: annotationSaving, + loading: annotationLoading, + currentImage, + selectedTemplate, + answer, + setAnswer, + handleTemplateChange, + openAnnotation, + closeAnnotation, + saveAnnotation, + refreshCurrentImage + } = useAnnotation(projectId, fetchImages, handleFindNextImage); + + useEffect(() => { + fetchImages(); + }, [projectId, page, imageName, hasQuestions, hasDatasets]); + + useEffect(() => { + setSelectedIds([]); + }, [viewMode]); + + // 处理导入成功 + const handleImportSuccess = () => { + setImportDialogOpen(false); + setPage(1); + fetchImages(); + }; + + // 处理生成问题 + const handleGenerateQuestions = image => { + setSelectedImage(image); + setQuestionDialogOpen(true); + }; + + // 处理生成数据集 + const handleGenerateDataset = image => { + setSelectedImage(image); + setDatasetDialogOpen(true); + }; + + // 删除图片 + const handleDeleteImage = async imageId => { + if (!confirm(t('images.deleteConfirm', { defaultValue: '确定要删除这张图片吗?' }))) { + return; + } + + try { + await axios.delete(`/api/projects/${projectId}/images?imageId=${imageId}`); + toast.success(t('images.deleteSuccess', { defaultValue: '删除成功' })); + fetchImages(); + } catch (error) { + console.error('Failed to delete image:', error); + toast.error(t('images.deleteFailed', { defaultValue: '删除失败' })); + } + }; + + // 批量删除图片 + const handleBatchDelete = async () => { + if (selectedIds.length === 0) { + toast.error(t('images.selectImagesToDelete', { defaultValue: '请选择要删除的图片' })); + return; + } + + if ( + !confirm( + t('images.batchDeleteConfirm', { + defaultValue: `确定要删除选中的 ${selectedIds.length} 张图片吗?`, + count: selectedIds.length + }) + ) + ) { + return; + } + + try { + setLoading(true); + let successCount = 0; + let failCount = 0; + + // 逐个调用删除接口 + for (const imageId of selectedIds) { + try { + await axios.delete(`/api/projects/${projectId}/images?imageId=${imageId}`); + successCount++; + } catch (error) { + console.error(`Failed to delete image ${imageId}:`, error); + failCount++; + } + } + + // 显示结果 + if (failCount === 0) { + toast.success( + t('images.batchDeleteSuccess', { + defaultValue: `成功删除 ${successCount} 张图片`, + count: successCount + }) + ); + } else { + toast.warning( + t('images.batchDeletePartialSuccess', { + defaultValue: `成功删除 ${successCount} 张,失败 ${failCount} 张`, + success: successCount, + fail: failCount + }) + ); + } + + // 清空选中状态并刷新列表 + setSelectedIds([]); + fetchImages(); + } catch (error) { + console.error('Batch delete failed:', error); + toast.error(t('images.batchDeleteFailed', { defaultValue: '批量删除失败' })); + } finally { + setLoading(false); + } + }; + + // 处理自动提取问题 + const handleAutoGenerateQuestions = () => { + if (!selectedModel) { + toast.error(t('images.selectModelFirst')); + return; + } + + if (selectedModel.type !== 'vision') { + toast.error(t('images.visionModelRequired')); + return; + } + + setAutoGenerateDialogOpen(true); + }; + + // 确认创建自动提取任务 + const handleConfirmAutoGenerate = async () => { + // 验证问题数量 + if (questionCount < 1 || questionCount > 10) { + toast.error(t('images.countRange')); + return; + } + + try { + setAutoGenerateDialogOpen(false); + + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'image-question-generation', + modelInfo: selectedModel, + language: i18n.language, + note: { questionCount } + }); + + if (response.data.code === 0) { + toast.success(t('images.taskCreated')); + // 跳转到任务管理页面 + router.push(`/projects/${projectId}/tasks`); + } else { + toast.error(response.data.error || t('images.taskCreateFailed')); + } + } catch (error) { + console.error('Failed to create auto-generate task:', error); + toast.error(t('images.taskCreateFailed')); + } + }; + + // 模板管理函数 + const handleOpenCreateTemplateDialog = () => { + setTemplateDialogOpen(true); + }; + + const handleCloseTemplateDialog = () => { + setTemplateDialogOpen(false); + }; + + const handleSubmitTemplate = async data => { + try { + await createTemplate(data); + handleCloseTemplateDialog(); + fetchImages(); + if (annotationOpen && currentImage) { + await refreshCurrentImage(); + } + toast.success(t('questions.operationSuccess')); + } catch (error) { + console.error('Failed to save template:', error); + } + }; + + return ( + + {/* 页面头部 */} + + + + {t('images.title', { defaultValue: '图片管理' })} + + + + {viewMode === 'list' && selectedIds.length > 0 && ( + + )} + + + + + + {/* 筛选区域 */} + + + {/* 图片列表 */} + {loading ? ( + + + + ) : viewMode === 'grid' ? ( + + ) : ( + + )} + + setImportDialogOpen(false)} + onSuccess={handleImportSuccess} + /> + + setQuestionDialogOpen(false)} + onSuccess={fetchImages} + /> + + setDatasetDialogOpen(false)} + onSuccess={fetchImages} + /> + + setAutoGenerateDialogOpen(false)} maxWidth="sm" fullWidth> + {t('images.autoGenerateQuestions')} + + {t('images.autoGenerateConfirm')} + + setQuestionCount(parseInt(e.target.value) || 1)} + inputProps={{ min: 1, max: 10 }} + helperText={t('images.questionCountHelp')} + sx={{ mb: 2 }} + /> + + + {t('images.currentModel')}: {selectedModel?.modelName || t('common.none')} + + + + + + + + + saveAnnotation(false)} + onSaveAndContinue={() => saveAnnotation(true)} + saving={annotationSaving} + loading={annotationLoading} + onOpenCreateQuestion={handleOpenCreateDialog} + onOpenCreateTemplate={handleOpenCreateTemplateDialog} + /> + + {/* 问题编辑对话框 */} + + + {/* 问题模板对话框 */} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/images/styles/imageStyles.js b/easy-dataset-main/app/projects/[projectId]/images/styles/imageStyles.js new file mode 100644 index 0000000..f46c80d --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/images/styles/imageStyles.js @@ -0,0 +1,286 @@ +/** + * 图片管理页面样式配置 + */ + +export const imageStyles = { + // 页面容器 + pageContainer: { + py: 4 + }, + + // 页面头部 + header: { + mb: 4, + display: 'flex', + justifyContent: 'space-between', + alignItems: 'flex-start', + flexWrap: 'wrap', + gap: 3 + }, + + headerTitle: { + display: 'flex', + flexDirection: 'column', + gap: 0.5 + }, + + title: { + fontWeight: 700 + }, + + subtitle: { + color: 'text.secondary', + fontSize: '0.875rem' + }, + + headerActions: { + display: 'flex', + gap: 2, + flexWrap: 'wrap' + }, + + actionButton: { + borderRadius: 2, + textTransform: 'none', + px: 3, + fontWeight: 600, + boxShadow: 2, + transition: 'all 0.3s ease', + '&:hover': { + transform: 'translateY(-2px)', + boxShadow: 4 + } + }, + + // 筛选区域 + filterCard: { + mb: 3, + borderRadius: 2, + boxShadow: 1, + border: '1px solid', + borderColor: 'divider', + overflow: 'visible' + }, + + filterContent: { + display: 'flex', + gap: 2, + alignItems: 'center', + flexWrap: 'wrap' + }, + + searchField: { + minWidth: { xs: '100%', sm: 300 }, + flex: { xs: '1 1 100%', sm: '1 1 auto' }, + '& .MuiOutlinedInput-root': { + borderRadius: 2 + } + }, + + filterSelect: { + minWidth: { xs: '48%', sm: 150 }, + '& .MuiOutlinedInput-root': { + borderRadius: 2 + } + }, + + viewToggle: { + ml: 'auto', + borderRadius: 2, + '& .MuiToggleButton-root': { + border: '1px solid', + borderColor: 'divider', + '&.Mui-selected': { + bgcolor: 'primary.main', + color: 'white', + '&:hover': { + bgcolor: 'primary.dark' + } + } + } + }, + + // 图片网格 + gridContainer: { + spacing: 3 + }, + + // 图片卡片 + imageCard: { + height: '100%', + display: 'flex', + flexDirection: 'column', + borderRadius: 3, + overflow: 'hidden', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + border: '1px solid', + borderColor: 'divider', + bgcolor: 'background.paper', + '&:hover': { + transform: 'translateY(-8px)', + boxShadow: theme => `0 12px 24px ${theme.palette.mode === 'dark' ? 'rgba(0,0,0,0.4)' : 'rgba(0,0,0,0.15)'}`, + borderColor: 'primary.main', + '& .image-overlay': { + opacity: 1 + } + } + }, + + imageWrapper: { + position: 'relative', + overflow: 'hidden', + bgcolor: 'grey.100' + }, + + imageMedia: { + height: 220, + objectFit: 'cover', + transition: 'transform 0.3s ease', + cursor: 'pointer', + '&:hover': { + transform: 'scale(1.05)' + } + }, + + imageOverlay: { + className: 'image-overlay', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: + 'linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 40%, transparent 60%, rgba(0,0,0,0.6) 100%)', + opacity: 0, + transition: 'opacity 0.3s ease', + pointerEvents: 'none' + }, + + statusChipsContainer: { + position: 'absolute', + top: 12, + right: 12, + display: 'flex', + gap: 0.5, + flexDirection: 'column', + alignItems: 'flex-end', + zIndex: 2 + }, + + statusChip: { + backdropFilter: 'blur(10px)', + fontWeight: 600, + fontSize: '0.75rem', + height: 24, + boxShadow: 2 + }, + + imageNameContainer: { + position: 'absolute', + bottom: 12, + left: 12, + right: 12, + display: 'flex', + justifyContent: 'center', + zIndex: 2 + }, + + imageNameChip: { + backdropFilter: 'blur(10px)', + bgcolor: 'rgba(255, 255, 255, 0.95)', + fontWeight: 600, + maxWidth: '90%', + boxShadow: 2, + '& .MuiChip-label': { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + } + }, + + cardContent: { + flexGrow: 1, + p: 2, + pb: 1.5 + }, + + imageName: { + fontWeight: 600, + fontSize: '0.9rem', + lineHeight: 1.4 + }, + + cardActions: { + p: 2, + pt: 0, + gap: 1, + mt: 2, + display: 'flex', + justifyContent: 'space-between' + }, + + actionIconButton: { + transition: 'all 0.2s ease', + '&:hover': { + transform: 'scale(1.1)' + } + }, + + primaryActionButton: { + borderRadius: 2, + textTransform: 'none', + fontWeight: 600, + flex: 1 + }, + + // 分页 + pagination: { + display: 'flex', + justifyContent: 'center', + mt: 4 + }, + + // 空状态 + emptyState: { + textAlign: 'center', + py: 12, + px: 3 + }, + + emptyIcon: { + width: 120, + height: 120, + borderRadius: '50%', + bgcolor: 'primary.lighter', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + mx: 'auto', + mb: 3 + }, + + emptyTitle: { + fontWeight: 600, + mb: 1 + }, + + emptyDescription: { + color: 'text.secondary', + mb: 4 + }, + + emptyButton: { + borderRadius: 2, + px: 4, + textTransform: 'none', + fontWeight: 600 + }, + + // 加载状态 + loadingContainer: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + py: 8 + } +}; diff --git a/easy-dataset-main/app/projects/[projectId]/layout.js b/easy-dataset-main/app/projects/[projectId]/layout.js new file mode 100644 index 0000000..b648530 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/layout.js @@ -0,0 +1,125 @@ +'use client'; + +import Navbar from '@/components/Navbar/index'; +import { useState, useEffect } from 'react'; +import { Box, CircularProgress, Typography, Button } from '@mui/material'; +import { useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { useSetAtom } from 'jotai'; +import { modelConfigListAtom, selectedModelInfoAtom } from '@/lib/store'; + +export default function ProjectLayout({ children, params }) { + const router = useRouter(); + const { projectId } = params; + const [projects, setProjects] = useState([]); + const [currentProject, setCurrentProject] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [t] = useTranslation(); + const setModelConfigList = useSetAtom(modelConfigListAtom); + const setSelectedModelInfo = useSetAtom(selectedModelInfoAtom); + + const fetchData = async () => { + try { + setLoading(true); + + const [projectsResponse, projectResponse, modelConfigResponse] = await Promise.all([ + fetch('/api/projects'), + fetch(`/api/projects/${projectId}`), + fetch(`/api/projects/${projectId}/model-config`) + ]); + + if (!projectsResponse.ok) { + throw new Error(t('projects.fetchFailed')); + } + const projectsData = await projectsResponse.json(); + setProjects(projectsData); + + if (!projectResponse.ok) { + if (projectResponse.status === 404) { + router.push('/'); + return; + } + throw new Error('Failed to load project details'); + } + const projectData = await projectResponse.json(); + setCurrentProject(projectData); + + if (modelConfigResponse.ok) { + const modelConfigData = await modelConfigResponse.json(); + const modelList = Array.isArray(modelConfigData?.data) ? modelConfigData.data : []; + setModelConfigList(modelList); + if (modelConfigData?.defaultModelConfigId) { + const defaultModel = modelList.find(item => item.id === modelConfigData.defaultModelConfigId); + setSelectedModelInfo(defaultModel || null); + } else { + setSelectedModelInfo(null); + } + } else { + setModelConfigList([]); + setSelectedModelInfo(null); + } + } catch (error) { + console.error('Failed to load project data:', error); + setError(error.message); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (!projectId || projectId === 'undefined') { + router.push('/'); + return; + } + + fetchData(); + }, [projectId, router]); + + if (loading) { + return ( + + + Loading project data... + + ); + } + + if (error) { + return ( + + + {t('projects.fetchFailed')}: {error} + + + + ); + } + + return ( + <> + + + {children} + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/[conversationId]/page.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/[conversationId]/page.js new file mode 100644 index 0000000..6e4cdcb --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/[conversationId]/page.js @@ -0,0 +1,139 @@ +'use client'; + +import { + Container, + Box, + Typography, + Alert, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Paper +} from '@mui/material'; +import ConversationHeader from '@/components/conversations/ConversationHeader'; +import ConversationMetadata from '@/components/conversations/ConversationMetadata'; +import ConversationContent from '@/components/conversations/ConversationContent'; +import ConversationRatingSection from '@/components/conversations/ConversationRatingSection'; +import useConversationDetails from './useConversationDetails'; +import { useTranslation } from 'react-i18next'; + +/** + * 多轮对话详情页面 + */ +export default function ConversationDetailPage({ params }) { + const { projectId, conversationId } = params; + const { t } = useTranslation(); + + // 使用自定义Hook管理状态和逻辑 + const { + conversation, + messages, + loading, + editMode, + saving, + editData, + setEditData, + deleteDialogOpen, + setDeleteDialogOpen, + handleEdit, + handleSave, + handleCancel, + handleDelete, + handleNavigate, + updateMessageContent + } = useConversationDetails(projectId, conversationId); + + // 加载状态 + if (loading) { + return ( + + + {t('datasets.loadingDataset')} + + + ); + } + + // 无数据状态 + if (!conversation) { + return ( + + {t('datasets.conversationNotFound')} + + ); + } + + return ( + + {/* 顶部导航栏 */} + setDeleteDialogOpen(true)} + onNavigate={handleNavigate} + /> + + {/* 主要布局:左右分栏 */} + + {/* 左侧主要内容区域 */} + + + {/* 对话内容 */} + + + + + {/* 右侧固定侧边栏 */} + + {/* 元数据展示 */} + + + {/* 评分、标签、备注区域 */} + { + // 更新成功后刷新数据,保持页面状态同步 + // 这里可以调用 useConversationDetails 的刷新逻辑 + }} + /> + + + + {/* 删除确认对话框 */} + setDeleteDialogOpen(false)}> + {t('datasets.confirmDelete')} + + {t('datasets.confirmDeleteConversation')} + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/[conversationId]/useConversationDetails.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/[conversationId]/useConversationDetails.js new file mode 100644 index 0000000..9e63397 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/[conversationId]/useConversationDetails.js @@ -0,0 +1,211 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; + +/** + * 多轮对话详情页面的状态管理Hook + */ +export default function useConversationDetails(projectId, conversationId) { + const { t } = useTranslation(); + const router = useRouter(); + + // 基础状态 + const [conversation, setConversation] = useState(null); + const [messages, setMessages] = useState([]); + const [loading, setLoading] = useState(true); + + // 编辑状态 + const [editMode, setEditMode] = useState(false); + const [saving, setSaving] = useState(false); + const [editData, setEditData] = useState({ + score: 0, + tags: '', + note: '', + confirmed: false, + messages: [] + }); + + // 对话框状态 + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + + // 获取对话详情 + const fetchConversation = async () => { + try { + setLoading(true); + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversationId}`); + + if (!response.ok) { + if (response.status === 404) { + toast.error(t('datasets.conversationNotFound')); + router.push(`/projects/${projectId}/multi-turn`); + return; + } + throw new Error(t('datasets.fetchDataFailed')); + } + + const data = await response.json(); + setConversation(data); + + // 解析对话消息 + let parsedMessages = []; + try { + parsedMessages = JSON.parse(data.rawMessages || '[]'); + setMessages(parsedMessages); + } catch (error) { + console.error('解析对话消息失败:', error); + setMessages([]); + } + + // 设置编辑数据 + setEditData({ + score: data.score || 0, + tags: data.tags || '', + note: data.note || '', + confirmed: data.confirmed || false, + messages: parsedMessages + }); + } catch (error) { + console.error('获取对话详情失败:', error); + toast.error(error.message || t('datasets.fetchDataFailed')); + } finally { + setLoading(false); + } + }; + + // 保存编辑 + const handleSave = async () => { + try { + setSaving(true); + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversationId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + score: editData.score, + tags: editData.tags, + note: editData.note, + confirmed: editData.confirmed, + messages: editData.messages + }) + }); + + if (!response.ok) { + throw new Error(t('datasets.saveFailed')); + } + + // 更新本地状态 + setConversation({ ...conversation, ...editData }); + setMessages(editData.messages); + setEditMode(false); + toast.success(t('datasets.saveSuccess')); + } catch (error) { + console.error('保存失败:', error); + toast.error(error.message || t('datasets.saveFailed')); + } finally { + setSaving(false); + } + }; + + // 开始编辑 + const handleEdit = () => { + setEditMode(true); + }; + + // 取消编辑 + const handleCancel = () => { + // 恢复到原始数据 + setEditData({ + score: conversation.score || 0, + tags: conversation.tags || '', + note: conversation.note || '', + confirmed: conversation.confirmed || false, + messages: messages + }); + setEditMode(false); + }; + + // 删除对话 + const handleDelete = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversationId}`, { + method: 'DELETE' + }); + + if (!response.ok) { + throw new Error(t('datasets.deleteFailed')); + } + + toast.success(t('datasets.deleteSuccess')); + router.push(`/projects/${projectId}/multi-turn`); + } catch (error) { + console.error('删除失败:', error); + toast.error(error.message || t('datasets.deleteFailed')); + } + }; + + // 更新消息内容 + const updateMessageContent = (index, newContent) => { + const updatedMessages = [...editData.messages]; + updatedMessages[index] = { ...updatedMessages[index], content: newContent }; + setEditData({ ...editData, messages: updatedMessages }); + }; + + // 翻页导航 + const handleNavigate = async direction => { + try { + const response = await fetch( + `/api/projects/${projectId}/dataset-conversations/${conversationId}?operateType=${direction}` + ); + + if (!response.ok) { + throw new Error('获取导航数据失败'); + } + + const data = await response.json(); + + if (data) { + router.push(`/projects/${projectId}/multi-turn/${data.id}`); + } else { + toast.warning(`已经是${direction === 'next' ? '最后' : '第'}一条对话了`); + } + } catch (error) { + console.error('导航失败:', error); + toast.error(error.message || '导航失败'); + } + }; + + // 初始化 + useEffect(() => { + fetchConversation(); + }, [projectId, conversationId]); + + return { + // 数据状态 + conversation, + messages, + loading, + + // 编辑状态 + editMode, + saving, + editData, + setEditData, + + // 对话框状态 + deleteDialogOpen, + setDeleteDialogOpen, + + // 操作方法 + handleEdit, + handleSave, + handleCancel, + handleDelete, + handleNavigate, + updateMessageContent, + fetchConversation + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/components/ConversationTable.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/ConversationTable.js new file mode 100644 index 0000000..f4b462b --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/ConversationTable.js @@ -0,0 +1,313 @@ +'use client'; + +import { + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + TablePagination, + IconButton, + Tooltip, + Typography, + Chip, + CircularProgress, + Checkbox +} from '@mui/material'; +import { Delete as DeleteIcon, Visibility as ViewIcon } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import RatingChip from './RatingChip'; + +const QUESTION_TOOLTIP_THRESHOLD = 80; +const SCENARIO_TOOLTIP_THRESHOLD = 120; + +const ConversationTable = ({ + conversations, + loading, + total, + page, + rowsPerPage, + onPageChange, + onRowsPerPageChange, + onView, + onDelete, + selectedIds = [], + onSelectionChange, + isAllSelected = false, + onSelectAll +}) => { + const { t } = useTranslation(); + const [expandedRows, setExpandedRows] = useState({}); + const columnWidths = { + checkbox: 52, + question: 280, + scenario: 340, + rounds: 90, + model: 120, + rating: 100, + createdAt: 110, + actions: 92 + }; + + const shouldShowTooltip = (value, threshold) => (value || '').length > threshold; + + const handleSelectOne = conversationId => { + if (selectedIds.includes(conversationId)) { + onSelectionChange(selectedIds.filter(id => id !== conversationId)); + } else { + onSelectionChange([...selectedIds, conversationId]); + } + }; + + const handleSelectAll = () => { + if (isAllSelected) { + onSelectionChange([]); + onSelectAll(false); + } else { + const currentPageIds = conversations.map(conv => conv.id); + onSelectionChange(currentPageIds); + onSelectAll(true); + } + }; + + const isIndeterminate = selectedIds.length > 0 && !isAllSelected; + const toggleRowExpanded = conversationId => { + setExpandedRows(prev => ({ ...prev, [conversationId]: !prev[conversationId] })); + }; + + return ( + + + + + + + + + {t('datasets.firstQuestion')} + + + {t('datasets.conversationScenario')} + + {t('datasets.conversationRounds')} + {t('datasets.modelUsed')} + {t('datasets.rating')} + {t('datasets.createTime')} + + {t('common.actions')} + + + + + {loading ? ( + + + + + + ) : conversations.length === 0 ? ( + + + + {t('datasets.noConversations')} + + + + ) : ( + conversations.map(conversation => { + const questionText = conversation.question || ''; + const scenarioText = conversation.scenario || ''; + const isExpanded = Boolean(expandedRows[conversation.id]); + const canToggleExpand = + questionText.length > QUESTION_TOOLTIP_THRESHOLD || scenarioText.length > SCENARIO_TOOLTIP_THRESHOLD; + + const questionContent = ( + + {questionText} + + ); + + const scenarioContent = ( + + + {scenarioText || t('datasets.notSet')} + + + ); + + return ( + + + handleSelectOne(conversation.id)} + /> + + + {shouldShowTooltip(questionText, QUESTION_TOOLTIP_THRESHOLD) ? ( + + {questionContent} + + ) : ( + questionContent + )} + {conversation.confirmed && ( + + )} + {canToggleExpand && ( + toggleRowExpanded(conversation.id)} + > + {isExpanded ? t('common.collapse') : t('common.expand')} + + )} + + + {shouldShowTooltip(scenarioText, SCENARIO_TOOLTIP_THRESHOLD) ? ( + + {scenarioContent} + + ) : ( + scenarioContent + )} + + + + {conversation.turnCount}/{conversation.maxTurns} + + + + + + + + + + {new Date(conversation.createAt).toLocaleDateString()} + + + + onView(conversation.id)}> + + + + + onDelete(conversation.id)}> + + + + + + ); + }) + )} + +
+ + onPageChange(newPage)} + rowsPerPage={rowsPerPage} + rowsPerPageOptions={[20, 50, 100]} + onRowsPerPageChange={event => { + onRowsPerPageChange(parseInt(event.target.value, 10)); + }} + labelRowsPerPage={t('datasets.rowsPerPage')} + /> +
+ ); +}; + +export default ConversationTable; diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/components/FilterDialog.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/FilterDialog.js new file mode 100644 index 0000000..2c96bf9 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/FilterDialog.js @@ -0,0 +1,104 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + FormControl, + InputLabel, + Select, + MenuItem +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +/** + * 筛选对话框组件 + * @param {boolean} open - 对话框开启状态 + * @param {function} onClose - 关闭回调 + * @param {object} filters - 筛选条件 + * @param {function} onFiltersChange - 筛选条件变化回调 + * @param {function} onReset - 重置回调 + * @param {function} onApply - 应用回调 + */ +const FilterDialog = ({ open, onClose, filters, onFiltersChange, onReset, onApply }) => { + const { t } = useTranslation(); + + const handleFilterChange = (field, value) => { + onFiltersChange({ ...filters, [field]: value }); + }; + + return ( + + {t('datasets.filtersTitle')} + + + handleFilterChange('roleA', e.target.value)} + fullWidth + /> + handleFilterChange('roleB', e.target.value)} + fullWidth + /> + handleFilterChange('scenario', e.target.value)} + fullWidth + /> + + handleFilterChange('scoreMin', e.target.value)} + fullWidth + /> + handleFilterChange('scoreMax', e.target.value)} + fullWidth + /> + + + {t('datasets.filterConfirmationStatus')} + + + + + + + + + + + ); +}; + +export default FilterDialog; diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/components/RatingChip.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/RatingChip.js new file mode 100644 index 0000000..d9c2be6 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/RatingChip.js @@ -0,0 +1,33 @@ +'use client'; + +import { Chip } from '@mui/material'; +import { Star as StarIcon } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import { getRatingConfigI18n, formatScore } from '@/components/datasets/utils/ratingUtils'; + +/** + * 评分展示组件 + * @param {number} score - 评分值 + */ +const RatingChip = ({ score }) => { + const { t } = useTranslation(); + const config = getRatingConfigI18n(score, t); + + return ( + } + label={`${formatScore(score)} ${config.label}`} + size="small" + sx={{ + backgroundColor: config.backgroundColor, + color: config.color, + fontWeight: 'medium', + '& .MuiChip-icon': { + color: config.color + } + }} + /> + ); +}; + +export default RatingChip; diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/components/SearchBar.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/SearchBar.js new file mode 100644 index 0000000..67bedde --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/components/SearchBar.js @@ -0,0 +1,92 @@ +'use client'; + +import { Box, Paper, Button, IconButton, InputBase, CircularProgress } from '@mui/material'; +import { + Search as SearchIcon, + FilterList as FilterIcon, + Download as DownloadIcon, + Delete as DeleteIcon +} from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; + +/** + * 搜索栏组件 + * @param {string} searchKeyword - 搜索关键词 + * @param {function} onSearchChange - 搜索关键词变化回调 + * @param {function} onSearch - 搜索回调 + * @param {function} onFilterClick - 筛选按钮点击回调 + * @param {function} onExportClick - 导出按钮点击回调 + * @param {boolean} exportLoading - 导出加载状态 + * @param {number} selectedCount - 选中的项目数量 + * @param {function} onBatchDelete - 批量删除回调 + * @param {boolean} batchDeleteLoading - 批量删除加载状态 + */ +const SearchBar = ({ + searchKeyword, + onSearchChange, + onSearch, + onFilterClick, + onExportClick, + exportLoading = false, + selectedCount = 0, + onBatchDelete, + batchDeleteLoading = false +}) => { + const { t } = useTranslation(); + + return ( + + + + + + + onSearchChange(e.target.value)} + onKeyPress={e => e.key === 'Enter' && onSearch()} + /> + + + + + {selectedCount > 0 && ( + + )} + + + + ); +}; + +export default SearchBar; diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/hooks/useMultiTurnData.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/hooks/useMultiTurnData.js new file mode 100644 index 0000000..9e11506 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/hooks/useMultiTurnData.js @@ -0,0 +1,346 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; + +/** + * Multi-turn dataset data hook + * @param {string} projectId + */ +export const useMultiTurnData = projectId => { + const { t } = useTranslation(); + const router = useRouter(); + + const [conversations, setConversations] = useState([]); + const [loading, setLoading] = useState(true); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(20); + const [total, setTotal] = useState(0); + const [searchKeyword, setSearchKeyword] = useState(''); + const [filterDialogOpen, setFilterDialogOpen] = useState(false); + const [exportLoading, setExportLoading] = useState(false); + + const [selectedIds, setSelectedIds] = useState([]); + const [isAllSelected, setIsAllSelected] = useState(false); + const [batchDeleteLoading, setBatchDeleteLoading] = useState(false); + + const [filters, setFilters] = useState({ + roleA: '', + roleB: '', + scenario: '', + scoreMin: '', + scoreMax: '', + confirmed: '' + }); + + const abortRef = useRef(null); + + const buildQuery = ({ pageIndex, keyword, filterValues }) => { + const params = new URLSearchParams({ + page: String(pageIndex + 1), + pageSize: String(rowsPerPage) + }); + + if (keyword) params.append('keyword', keyword); + if (filterValues.roleA) params.append('roleA', filterValues.roleA); + if (filterValues.roleB) params.append('roleB', filterValues.roleB); + if (filterValues.scenario) params.append('scenario', filterValues.scenario); + if (filterValues.scoreMin) params.append('scoreMin', filterValues.scoreMin); + if (filterValues.scoreMax) params.append('scoreMax', filterValues.scoreMax); + if (filterValues.confirmed) params.append('confirmed', filterValues.confirmed); + + return params; + }; + + const fetchConversations = async (newPage = page, options = {}) => { + const keyword = options.keyword ?? searchKeyword; + const filterValues = options.filterValues ?? filters; + const showLoading = options.showLoading ?? true; + + try { + if (abortRef.current) { + abortRef.current.abort(); + } + const controller = new AbortController(); + abortRef.current = controller; + + if (showLoading) { + setLoading(true); + } + + const params = buildQuery({ pageIndex: newPage, keyword, filterValues }); + const response = await fetch(`/api/projects/${projectId}/dataset-conversations?${params.toString()}`, { + signal: controller.signal + }); + + if (!response.ok) { + throw new Error(t('datasets.fetchDataFailed')); + } + + const data = await response.json(); + setConversations(data.data || []); + setTotal(data.total || 0); + } catch (error) { + if (error?.name === 'AbortError') return; + console.error('Failed to fetch multi-turn dataset list:', error); + toast.error(error.message || t('datasets.fetchDataFailed')); + } finally { + if (showLoading) { + setLoading(false); + } + if (abortRef.current === controller) { + abortRef.current = null; + } + } + }; + + const handleExport = async () => { + try { + setExportLoading(true); + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/export`); + + if (!response.ok) { + throw new Error(t('datasets.exportFailed')); + } + + const data = await response.json(); + const dataStr = JSON.stringify(data, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + link.download = `multi-turn-conversations-${projectId}-${new Date().toISOString().slice(0, 10)}.json`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + + toast.success(t('datasets.exportSuccess')); + } catch (error) { + console.error('Export failed:', error); + toast.error(error.message || t('datasets.exportFailed')); + } finally { + setExportLoading(false); + } + }; + + const fetchAllConversationIds = async () => { + try { + const params = new URLSearchParams({ getAllIds: 'true' }); + if (searchKeyword) params.append('keyword', searchKeyword); + if (filters.roleA) params.append('roleA', filters.roleA); + if (filters.roleB) params.append('roleB', filters.roleB); + if (filters.scenario) params.append('scenario', filters.scenario); + if (filters.scoreMin) params.append('scoreMin', filters.scoreMin); + if (filters.scoreMax) params.append('scoreMax', filters.scoreMax); + if (filters.confirmed) params.append('confirmed', filters.confirmed); + + const response = await fetch(`/api/projects/${projectId}/dataset-conversations?${params.toString()}`); + if (!response.ok) { + throw new Error(t('datasets.fetchDataFailed')); + } + + const data = await response.json(); + return data.allConversationIds || []; + } catch (error) { + console.error('Failed to fetch all conversation IDs:', error); + toast.error(error.message || t('datasets.fetchDataFailed')); + return []; + } + }; + + const handleDelete = async conversationId => { + if (!confirm(t('datasets.confirmDeleteConversation'))) { + return; + } + + try { + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversationId}`, { + method: 'DELETE' + }); + + if (!response.ok) { + throw new Error(t('datasets.deleteFailed')); + } + + toast.success(t('datasets.deleteSuccess')); + fetchConversations(); + } catch (error) { + console.error('Delete failed:', error); + toast.error(error.message || t('datasets.deleteFailed')); + } + }; + + const deleteConversationsConcurrently = async (conversationIds, concurrency = 10) => { + const results = []; + const errors = []; + + for (let i = 0; i < conversationIds.length; i += concurrency) { + const batch = conversationIds.slice(i, i + concurrency); + const promises = batch.map(async id => { + try { + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${id}`, { + method: 'DELETE' + }); + if (!response.ok) { + throw new Error(`Delete conversation ${id} failed`); + } + return { id, success: true }; + } catch (error) { + errors.push({ id, error: error.message }); + return { id, success: false, error: error.message }; + } + }); + + const batchResults = await Promise.all(promises); + results.push(...batchResults); + } + + return { results, errors }; + }; + + const handleBatchDelete = async () => { + let idsToDelete = selectedIds; + + if (isAllSelected) { + idsToDelete = await fetchAllConversationIds(); + if (idsToDelete.length === 0) { + toast.error(t('datasets.noDataToDelete')); + return; + } + } + + if (idsToDelete.length === 0) { + toast.error(t('datasets.pleaseSelectData')); + return; + } + + if (!confirm(t('common.confirmDelete', { count: idsToDelete.length }))) { + return; + } + + try { + setBatchDeleteLoading(true); + const { results, errors } = await deleteConversationsConcurrently(idsToDelete); + + const successCount = results.filter(r => r.success).length; + const failCount = errors.length; + + if (failCount === 0) { + toast.success(t('common.deleteSuccess', { count: successCount })); + } else { + toast.warning(t('datasets.batchDeletePartialSuccess', { success: successCount, fail: failCount })); + } + + setSelectedIds([]); + setIsAllSelected(false); + fetchConversations(); + } catch (error) { + console.error('Batch delete failed:', error); + toast.error(error.message || t('datasets.batchDeleteFailed')); + } finally { + setBatchDeleteLoading(false); + } + }; + + const handleSelectionChange = newSelectedIds => { + setSelectedIds(newSelectedIds); + if (newSelectedIds.length === 0) { + setIsAllSelected(false); + } + }; + + const handleSelectAll = selectAll => { + setIsAllSelected(selectAll); + if (!selectAll) { + setSelectedIds([]); + } + }; + + const handleView = conversationId => { + router.push(`/projects/${projectId}/multi-turn/${conversationId}`); + }; + + const applyFilters = () => { + setPage(0); + setFilterDialogOpen(false); + fetchConversations(0, { keyword: searchKeyword, filterValues: filters }); + }; + + const resetFilters = () => { + const clearedFilters = { + roleA: '', + roleB: '', + scenario: '', + scoreMin: '', + scoreMax: '', + confirmed: '' + }; + setFilters(clearedFilters); + setSearchKeyword(''); + setPage(0); + fetchConversations(0, { keyword: '', filterValues: clearedFilters }); + }; + + const handleSearch = () => { + setPage(0); + fetchConversations(0, { keyword: searchKeyword, filterValues: filters }); + }; + + const handlePageChange = newPage => { + setPage(newPage); + }; + + const handleRowsPerPageChange = newRowsPerPage => { + setRowsPerPage(newRowsPerPage); + setPage(0); + }; + + useEffect(() => { + fetchConversations(page, { showLoading: true }); + }, [projectId, page, rowsPerPage]); + + useEffect(() => { + return () => { + if (abortRef.current) { + abortRef.current.abort(); + } + }; + }, []); + + return { + conversations, + loading, + page, + rowsPerPage, + total, + searchKeyword, + filterDialogOpen, + exportLoading, + filters, + + selectedIds, + isAllSelected, + batchDeleteLoading, + + setSearchKeyword, + setFilterDialogOpen, + setFilters, + + fetchConversations, + handleExport, + handleDelete, + handleView, + applyFilters, + resetFilters, + handleSearch, + handlePageChange, + handleRowsPerPageChange, + + handleBatchDelete, + handleSelectionChange, + handleSelectAll + }; +}; diff --git a/easy-dataset-main/app/projects/[projectId]/multi-turn/page.js b/easy-dataset-main/app/projects/[projectId]/multi-turn/page.js new file mode 100644 index 0000000..cc8c8c4 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/multi-turn/page.js @@ -0,0 +1,106 @@ +'use client'; + +import { Container, Typography, Box, Card, useTheme, alpha } from '@mui/material'; +import { Chat as ChatIcon } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; + +// 导入拆分后的组件 +import SearchBar from './components/SearchBar'; +import ConversationTable from './components/ConversationTable'; +import FilterDialog from './components/FilterDialog'; +import { useMultiTurnData } from './hooks/useMultiTurnData'; + +export default function MultiTurnDatasetPage({ params }) { + const { t } = useTranslation(); + const theme = useTheme(); + const { projectId } = params; + + // 使用自定义Hook管理状态和逻辑 + const { + conversations, + loading, + page, + rowsPerPage, + total, + searchKeyword, + filterDialogOpen, + exportLoading, + filters, + selectedIds, + isAllSelected, + batchDeleteLoading, + setSearchKeyword, + setFilterDialogOpen, + setFilters, + fetchConversations, + handleExport, + handleDelete, + handleView, + applyFilters, + resetFilters, + handleSearch, + handlePageChange, + handleRowsPerPageChange, + handleBatchDelete, + handleSelectionChange, + handleSelectAll + } = useMultiTurnData(projectId); + + return ( + + + {/* + + + {t('datasets.multiTurnConversations')} + + */} + + setFilterDialogOpen(true)} + onExportClick={handleExport} + exportLoading={exportLoading} + selectedCount={isAllSelected ? total : selectedIds.length} + onBatchDelete={handleBatchDelete} + batchDeleteLoading={batchDeleteLoading} + /> + + + + + setFilterDialogOpen(false)} + filters={filters} + onFiltersChange={setFilters} + onReset={resetFilters} + onApply={applyFilters} + /> + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/page.js b/easy-dataset-main/app/projects/[projectId]/page.js new file mode 100644 index 0000000..4f529cb --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/page.js @@ -0,0 +1,39 @@ +'use client'; + +import { useEffect } from 'react'; +import { useRouter } from 'next/navigation'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useSetAtom } from 'jotai/index'; +import { modelConfigListAtom, selectedModelInfoAtom } from '@/lib/store'; + +export default function ProjectPage({ params }) { + const router = useRouter(); + const setConfigList = useSetAtom(modelConfigListAtom); + const setSelectedModelInfo = useSetAtom(selectedModelInfoAtom); + const { projectId } = params; + + // 默认重定向到文本分割页面 + useEffect(() => { + getModelConfigList(projectId); + router.push(`/projects/${projectId}/text-split`); + }, [projectId, router]); + + const getModelConfigList = projectId => { + axios + .get(`/api/projects/${projectId}/model-config`) + .then(response => { + setConfigList(response.data.data); + if (response.data.defaultModelConfigId) { + setSelectedModelInfo(response.data.data.find(item => item.id === response.data.defaultModelConfigId)); + } else { + setSelectedModelInfo(null); + } + }) + .catch(error => { + toast.error('get model list error'); + }); + }; + + return null; +} diff --git a/easy-dataset-main/app/projects/[projectId]/playground/page.js b/easy-dataset-main/app/projects/[projectId]/playground/page.js new file mode 100644 index 0000000..faf5378 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/playground/page.js @@ -0,0 +1,92 @@ +'use client'; + +import React from 'react'; +import { Box, Typography, Paper, Alert } from '@mui/material'; +import { useParams } from 'next/navigation'; +import { useTheme } from '@mui/material/styles'; +import ChatArea from '@/components/playground/ChatArea'; +import MessageInput from '@/components/playground/MessageInput'; +import PlaygroundHeader from '@/components/playground/PlaygroundHeader'; +import useModelPlayground from '@/hooks/useModelPlayground'; +import { playgroundStyles } from '@/styles/playground'; +import { useTranslation } from 'react-i18next'; +import { useAtomValue } from 'jotai/index'; +import { modelConfigListAtom } from '@/lib/store'; + +export default function ModelPlayground({ searchParams }) { + const theme = useTheme(); + const params = useParams(); + const { projectId } = params; + const modelId = searchParams?.modelId || null; + const styles = playgroundStyles(theme); + const { t } = useTranslation(); + + const { + selectedModels, + loading, + userInput, + conversations, + error, + outputMode, + uploadedImage, + handleModelSelection, + handleInputChange, + handleImageUpload, + handleRemoveImage, + handleSendMessage, + handleClearConversations, + handleOutputModeChange + } = useModelPlayground(projectId, modelId); + + const availableModels = useAtomValue(modelConfigListAtom); + + // 获取模型名称 + const getModelName = modelId => { + const model = availableModels.find(m => m.id === modelId); + return model ? `${model.providerName}: ${model.modelName}` : modelId; + }; + return ( + + + {t('playground.title')} + + + {error && ( + + {error} + + )} + + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/ConfirmDialog.js b/easy-dataset-main/app/projects/[projectId]/questions/components/ConfirmDialog.js new file mode 100644 index 0000000..e7f5753 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/ConfirmDialog.js @@ -0,0 +1,58 @@ +'use client'; + +import { Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Button } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +/** + * 确认对话框组件 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调函数 + * @param {Function} props.onConfirm - 确认操作的回调函数 + * @param {string} props.title - 对话框标题 + * @param {string} props.content - 对话框内容 + * @param {string} props.confirmText - 确认按钮文本,默认为 "确认删除" + * @param {string} props.cancelText - 取消按钮文本,默认为 "取消" + * @param {string} props.confirmColor - 确认按钮颜色,默认为 "error" + */ +export default function ConfirmDialog({ + open, + onClose, + onConfirm, + title, + content, + confirmText, + cancelText, + confirmColor = 'error' +}) { + const { t } = useTranslation(); + + const handleConfirm = () => { + onClose(); + if (onConfirm) { + onConfirm(); + } + }; + + return ( + + {title} + + {content} + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/ExportQuestionsDialog.js b/easy-dataset-main/app/projects/[projectId]/questions/components/ExportQuestionsDialog.js new file mode 100644 index 0000000..78f7dfa --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/ExportQuestionsDialog.js @@ -0,0 +1,82 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + FormControl, + FormLabel, + RadioGroup, + FormControlLabel, + Radio, + Box, + Divider +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import DownloadIcon from '@mui/icons-material/Download'; + +export default function ExportQuestionsDialog({ open, onClose, onExport, selectedCount, totalCount }) { + const { t } = useTranslation(); + const [format, setFormat] = useState('json'); + const [exportScope, setExportScope] = useState('all'); + + const handleExport = () => { + const exportOptions = { + format, + selectedIds: exportScope === 'selected' ? [] : undefined + }; + + onExport(exportOptions); + onClose(); + }; + + return ( + + {t('questions.exportQuestions')} + + + {/* 导出范围 */} + + {t('questions.exportScope')} + setExportScope(e.target.value)}> + } + label={t('questions.exportAll', { count: totalCount })} + /> + {selectedCount > 0 && ( + } + label={t('questions.exportSelected', { count: selectedCount })} + /> + )} + + + + + + {/* 导出格式 */} + + {t('questions.exportFormat')} + setFormat(e.target.value)}> + } label="JSON" /> + } label="JSONL" /> + } label={t('questions.txtFormat')} /> + } label="CSV" /> + + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionEditDialog.js b/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionEditDialog.js new file mode 100644 index 0000000..b4ed4b5 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionEditDialog.js @@ -0,0 +1,220 @@ +'use client'; + +import { useState, useEffect, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Box, + Autocomplete, + TextField as MuiTextField, + FormControl, + InputLabel, + Select, + MenuItem +} from '@mui/material'; +import axios from 'axios'; + +export default function QuestionEditDialog({ + open, + onClose, + onSubmit, + initialData, + projectId, + tags, + mode = 'create' // 'create' or 'edit' +}) { + const [chunks, setChunks] = useState([]); + const [images, setImages] = useState([]); + const { t } = useTranslation(); + + // 获取文本块的标题 + const getChunkTitle = chunkId => { + const chunk = chunks.find(c => c.id === chunkId); + return chunk?.name || chunkId; // 直接使用文件名 + }; + + const [formData, setFormData] = useState({ + id: '', + question: '', + sourceType: 'text', // 新增:数据源类型 + chunkId: '', + imageId: '', // 新增:图片ID + label: '' // 默认不选中任何标签 + }); + + const getChunks = async projectId => { + // 获取文本块列表 + const response = await axios.get(`/api/projects/${projectId}/split`); + if (response.status !== 200) { + throw new Error(t('common.fetchError')); + } + setChunks(response.data.chunks || []); + }; + + const getImages = async projectId => { + // 获取图片列表(只获取ID和名称) + try { + const response = await axios.get(`/api/projects/${projectId}/images?page=1&pageSize=10000&simple=true`); + if (response.status === 200) { + setImages(response.data.data || []); + } + } catch (error) { + console.error('Failed to fetch images:', error); + } + }; + + useEffect(() => { + getChunks(projectId); + getImages(projectId); + if (initialData) { + // 根据 imageId 判断数据源类型 + console.log('initialData:', initialData); + const sourceType = initialData.imageId ? 'image' : 'text'; + setFormData({ + id: initialData.id, + question: initialData.question || '', + sourceType: sourceType, + chunkId: initialData.chunkId || '', + imageId: initialData.imageId || '', + label: initialData.label || 'other' // 改用 label 而不是 label + }); + } else { + setFormData({ + id: '', + question: '', + sourceType: 'text', + chunkId: '', + imageId: '', + label: '' + }); + } + }, [initialData]); + + const handleSubmit = () => { + onSubmit(formData); + onClose(); + }; + + const flattenTags = (tags = [], prefix = '') => { + let flatTags = []; + const traverse = node => { + flatTags.push({ + id: node.label, // 使用标签名作为 id + label: node.label, // 直接使用原始标签名 + originalLabel: node.label + }); + if (node.child && node.child.length > 0) { + node.child.forEach(child => traverse(child)); + } + }; + tags.forEach(tag => traverse(tag)); + flatTags.push({ + id: 'other', + label: t('datasets.uncategorized'), + originalLabel: 'other' + }); + return flatTags; + }; + + const flattenedTags = useMemo(() => flattenTags(tags), [tags, t]); + + return ( + + {mode === 'create' ? t('questions.createQuestion') : t('questions.editQuestion')} + + + {/* 数据源类型选择 */} + + {t('questions.sourceType', { defaultValue: '数据源类型' })} + + + + {/* 问题内容 */} + setFormData({ ...formData, question: e.target.value })} + /> + + {/* 文本块选择(仅当数据源为文本时显示) */} + {formData.sourceType === 'text' && ( + getChunkTitle(chunk.id)} + value={chunks.find(chunk => chunk.id === formData.chunkId) || null} + onChange={(e, newValue) => setFormData({ ...formData, chunkId: newValue ? newValue.id : '' })} + renderInput={params => ( + + )} + /> + )} + + {/* 图片选择(仅当数据源为图片时显示) */} + {formData.sourceType === 'image' && ( + image.imageName || ''} + value={images.find(image => image.id === formData.imageId) || null} + onChange={(e, newValue) => setFormData({ ...formData, imageId: newValue ? newValue.id : '' })} + renderInput={params => ( + + )} + /> + )} + + {/* 标签选择 */} + {formData.sourceType === 'text' && ( + tag.label} + value={flattenedTags.find(tag => tag.id === formData.label) || null} + onChange={(e, newValue) => setFormData({ ...formData, label: newValue ? newValue.id : '' })} + renderInput={params => ( + + )} + /> + )} + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionsFilter.js b/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionsFilter.js new file mode 100644 index 0000000..c417903 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionsFilter.js @@ -0,0 +1,192 @@ +'use client'; + +import { Box, Stack, Checkbox, Typography, TextField, InputAdornment, Select, MenuItem, useTheme } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import SearchIcon from '@mui/icons-material/Search'; + +export default function QuestionsFilter({ + // 选择相关 + selectedQuestionsCount, + totalQuestions, + isAllSelected, + isIndeterminate, + onSelectAll, + + // 搜索相关 + searchTerm, + onSearchChange, + searchMatchMode, + onSearchMatchModeChange, + + // 过滤相关 + answerFilter, + onFilterChange, + + // 文本块名称筛选 + chunkNameFilter, + onChunkNameFilterChange, + + // 数据源类型筛选 + sourceTypeFilter, + onSourceTypeFilterChange, + + activeTab +}) { + const { t } = useTranslation(); + const theme = useTheme(); + + if (activeTab === 1) { + return <>; + } + return ( + + + {/* 选择区域 */} + + + + {selectedQuestionsCount > 0 + ? t('questions.selectedCount', { count: selectedQuestionsCount }) + : t('questions.selectAll')} + ( + {t('questions.totalCount', { + count: totalQuestions + })} + ) + + + + {/* 搜索和过滤区域 */} + + {/* 组合搜索框:下拉选择(匹配/不匹配)+ 输入框 */} + + + + + + ) + }} + /> + + + + + ) + }} + /> + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionsPageHeader.js b/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionsPageHeader.js new file mode 100644 index 0000000..ac46d54 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/QuestionsPageHeader.js @@ -0,0 +1,192 @@ +'use client'; + +import { useState } from 'react'; +import { Box, Typography, Button, Tooltip, Menu, MenuItem, ListItemIcon, ListItemText } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import DatasetIcon from '@mui/icons-material/Dataset'; +import ChatIcon from '@mui/icons-material/Chat'; +import ImageIcon from '@mui/icons-material/Image'; +import LibraryAddIcon from '@mui/icons-material/LibraryAdd'; +import DownloadIcon from '@mui/icons-material/Download'; + +export default function QuestionsPageHeader({ + questionsTotal, + selectedQuestionsCount, + onBatchDeleteQuestions, + onOpenCreateDialog, + onOpenCreateTemplateDialog, + onBatchGenerateAnswers, + onAutoGenerateDatasets, + onAutoGenerateMultiTurnDatasets, + onAutoGenerateImageDatasets, + onExportQuestions, + activeTab +}) { + const { t } = useTranslation(); + const [anchorEl, setAnchorEl] = useState(null); + const [createAnchorEl, setCreateAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const createMenuOpen = Boolean(createAnchorEl); + + const handleMenuClick = event => { + setAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setAnchorEl(null); + }; + + const handleCreateMenuClick = event => { + setCreateAnchorEl(event.currentTarget); + }; + + const handleCreateMenuClose = () => { + setCreateAnchorEl(null); + }; + + const handleCreateQuestion = () => { + handleCreateMenuClose(); + onOpenCreateDialog(); + }; + + const handleCreateTemplate = () => { + handleCreateMenuClose(); + onOpenCreateTemplateDialog(); + }; + + const handleSingleTurnGenerate = () => { + handleMenuClose(); + onAutoGenerateDatasets(); + }; + + const handleMultiTurnGenerate = () => { + handleMenuClose(); + onAutoGenerateMultiTurnDatasets(); + }; + + const handleImageDatasetGenerate = () => { + handleMenuClose(); + onAutoGenerateImageDatasets(); + }; + + return ( + + + {t('questions.title')} ({questionsTotal}) + + + + + + + + + + + + + + + + + + + + + + + + {/* */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/TemplateListView.js b/easy-dataset-main/app/projects/[projectId]/questions/components/TemplateListView.js new file mode 100644 index 0000000..3232982 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/TemplateListView.js @@ -0,0 +1,121 @@ +'use client'; + +import { + Box, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + IconButton, + Chip, + Typography, + Alert +} from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { useTranslation } from 'react-i18next'; + +export default function TemplateListView({ templates, onEditTemplate, onDeleteTemplate, loading }) { + const { t } = useTranslation(); + + const getAnswerTypeLabel = type => { + const labels = { + text: t('questions.template.answerType.text'), + label: t('questions.template.answerType.tags'), + custom_format: t('questions.template.answerType.customFormat') + }; + return labels[type] || type; + }; + + const getSourceTypeLabel = type => { + const labels = { + image: t('questions.template.sourceType.image'), + text: t('questions.template.sourceType.text') + }; + return labels[type] || type; + }; + + if (loading) { + return ( + + {t('common.loading')} + + ); + } + + if (!templates || templates.length === 0) { + return ( + + {t('questions.template.noTemplates')} + + ); + } + + return ( + + + + + {t('questions.template.question')} + {t('questions.template.sourceType.label')} + {t('questions.template.answerType.label')} + {t('questions.template.description')} + {t('questions.template.used')} + {t('common.actions')} + + + + {templates.map(template => ( + + + + {template.question} + + + + + + + + + + + {template.description || '-'} + + + + {template.usageCount > 0 ? ( + + ) : ( + + 0 + + )} + + + onEditTemplate(template)} sx={{ mr: 1 }}> + + + onDeleteTemplate(template.id)} + disabled={template.usageCount > 0} + color="error" + > + + + + + ))} + +
+
+ ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/template/TemplateFormDialog.js b/easy-dataset-main/app/projects/[projectId]/questions/components/template/TemplateFormDialog.js new file mode 100644 index 0000000..38aca6c --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/template/TemplateFormDialog.js @@ -0,0 +1,335 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + FormControl, + InputLabel, + Select, + MenuItem, + Box, + Chip, + Typography, + Alert, + FormControlLabel, + Checkbox +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export default function TemplateFormDialog({ open, onClose, onSubmit, template }) { + const { t } = useTranslation(); + const [formData, setFormData] = useState({ + question: '', + sourceType: 'text', + answerType: 'text', + description: '', + labels: [], + customFormat: '', + autoGenerate: true + }); + const [labelInput, setLabelInput] = useState(''); + const [errors, setErrors] = useState({}); + const [showConfirmDialog, setShowConfirmDialog] = useState(false); + + useEffect(() => { + if (template) { + setFormData({ + question: template.question || '', + sourceType: template.sourceType || 'text', + answerType: template.answerType || 'text', + description: template.description || '', + labels: template.labels || [], + customFormat: template.customFormat ? JSON.stringify(template.customFormat, null, 2) : '', + autoGenerate: true // 编辑模式下默认不自动生成 + }); + } else { + setFormData({ + question: '', + sourceType: 'text', + answerType: 'text', + description: '', + labels: [], + customFormat: '', + autoGenerate: true + }); + } + setErrors({}); + setShowConfirmDialog(false); + }, [template, open]); + + const handleChange = (field, value) => { + setFormData(prev => ({ ...prev, [field]: value })); + // 清除该字段的错误 + if (errors[field]) { + setErrors(prev => ({ ...prev, [field]: null })); + } + }; + + const handleAddLabel = () => { + const trimmed = labelInput.trim(); + if (trimmed && !formData.labels.includes(trimmed)) { + setFormData(prev => ({ + ...prev, + labels: [...prev.labels, trimmed] + })); + setLabelInput(''); + } + }; + + const handleDeleteLabel = labelToDelete => { + setFormData(prev => ({ + ...prev, + labels: prev.labels.filter(label => label !== labelToDelete) + })); + }; + + const validate = () => { + const newErrors = {}; + + if (!formData.question.trim()) { + newErrors.question = t('questions.template.errors.questionRequired'); + } + + if (formData.answerType === 'label' && formData.labels.length === 0) { + newErrors.labels = t('questions.template.errors.labelsRequired'); + } + + if (formData.answerType === 'custom_format') { + if (!formData.customFormat.trim()) { + newErrors.customFormat = t('questions.template.errors.customFormatRequired'); + } else { + try { + JSON.parse(formData.customFormat); + } catch (e) { + newErrors.customFormat = t('questions.template.errors.invalidJson'); + } + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = () => { + if (!validate()) { + return; + } + + // 如果选择了自动生成,显示确认对话框 + if (formData.autoGenerate) { + setShowConfirmDialog(true); + return; + } + + // 直接提交 + submitTemplate(); + }; + + const submitTemplate = () => { + const submitData = { + question: formData.question.trim(), + sourceType: formData.sourceType, + answerType: formData.answerType, + description: formData.description.trim(), + autoGenerate: formData.autoGenerate, + templateId: template?.id // 编辑模式时传递模板ID,用于查找未创建问题的数据源 + }; + + if (formData.answerType === 'label') { + submitData.labels = formData.labels; + } + + if (formData.answerType === 'custom_format') { + try { + submitData.customFormat = JSON.parse(formData.customFormat); + } catch (e) { + // 已在验证中处理 + return; + } + } + + onSubmit(submitData); + setShowConfirmDialog(false); + }; + + const handleConfirmGenerate = () => { + submitTemplate(); + }; + + const handleCancelGenerate = () => { + setShowConfirmDialog(false); + }; + + return ( + + {template ? t('questions.template.edit') : t('questions.template.create')} + + + {/* 数据源类型 */} + + {t('questions.template.sourceTypeInfo')} + + + + {/* 问题内容 */} + handleChange('question', e.target.value)} + error={!!errors.question} + helperText={errors.question} + required + /> + + {/* 答案类型 */} + + {t('questions.template.answerType.label')} + + + + {/* 描述 */} + handleChange('description', e.target.value)} + helperText={t('questions.template.descriptionHelp')} + multiline + rows={2} + /> + + {/* 标签输入 (仅当答案类型为 label 时显示) */} + {formData.answerType === 'label' && ( + + + setLabelInput(e.target.value)} + onKeyPress={e => { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddLabel(); + } + }} + error={!!errors.labels} + helperText={errors.labels} + /> + + + + {formData.labels.map(label => ( + handleDeleteLabel(label)} + color="primary" + variant="outlined" + /> + ))} + + + )} + + {/* 自定义格式输入 (仅当答案类型为 custom_format 时显示) */} + {formData.answerType === 'custom_format' && ( + + handleChange('customFormat', e.target.value)} + multiline + rows={6} + error={!!errors.customFormat} + helperText={errors.customFormat || t('questions.template.customFormatHelp')} + placeholder='{"field1": "description", "field2": "description"}' + /> + + {t('questions.template.customFormatInfo')} + + + )} + + {/* 自动生成问题选项 */} + + handleChange('autoGenerate', e.target.checked)} + color="primary" + /> + } + label={t('questions.template.autoGenerate')} + /> + + {formData.sourceType === 'text' + ? t('questions.template.autoGenerateHelpText') + : t('questions.template.autoGenerateHelpImage')} + + + + + + + + + + {/* 自动生成确认对话框 */} + + {t('questions.template.confirmAutoGenerate')} + + + {template + ? formData.sourceType === 'text' + ? t('questions.template.confirmAutoGenerateEditTextMessage', { + defaultValue: '您选择了自动生成问题。系统将为所有还未创建此模板问题的文本块创建问题。' + }) + : t('questions.template.confirmAutoGenerateEditImageMessage', { + defaultValue: '您选择了自动生成问题。系统将为所有还未创建此模板问题的图片创建问题。' + }) + : formData.sourceType === 'text' + ? t('questions.template.confirmAutoGenerateTextMessage') + : t('questions.template.confirmAutoGenerateImageMessage')} + + + {t('questions.template.autoGenerateWarning')} + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/components/template/TemplateManagementDialog.js b/easy-dataset-main/app/projects/[projectId]/questions/components/template/TemplateManagementDialog.js new file mode 100644 index 0000000..6bf3c72 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/components/template/TemplateManagementDialog.js @@ -0,0 +1,165 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + List, + ListItem, + ListItemText, + ListItemSecondaryAction, + IconButton, + Chip, + Typography, + Alert, + Tabs, + Tab +} from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AddIcon from '@mui/icons-material/Add'; +import { useTranslation } from 'react-i18next'; +import TemplateFormDialog from './TemplateFormDialog'; + +export default function TemplateManagementDialog({ + open, + onClose, + templates, + onCreateTemplate, + onUpdateTemplate, + onDeleteTemplate, + loading +}) { + const { t } = useTranslation(); + const [formOpen, setFormOpen] = useState(false); + const [editingTemplate, setEditingTemplate] = useState(null); + const [currentTab, setCurrentTab] = useState(0); // 0: image, 1: text + + const handleCreate = () => { + setEditingTemplate(null); + setFormOpen(true); + }; + + const handleEdit = template => { + setEditingTemplate(template); + setFormOpen(true); + }; + + const handleDelete = async templateId => { + const confirmed = window.confirm(t('questions.template.deleteConfirm')); + if (confirmed) { + await onDeleteTemplate(templateId); + } + }; + + const handleFormSubmit = async data => { + // 根据当前tab添加sourceType + const sourceType = currentTab === 0 ? 'image' : 'text'; + const templateData = { ...data, sourceType }; + + if (editingTemplate) { + await onUpdateTemplate(editingTemplate.id, templateData); + } else { + await onCreateTemplate(templateData); + } + setFormOpen(false); + }; + + const getAnswerTypeLabel = type => { + const labels = { + text: t('questions.template.answerType.text'), + label: t('questions.template.answerType.tags'), + custom_format: t('questions.template.answerType.customFormat') + }; + return labels[type] || type; + }; + + // 按数据源类型分组模板 + const imageTemplates = templates.filter(t => t.sourceType === 'image'); + const textTemplates = templates.filter(t => t.sourceType === 'text'); + + const currentTemplates = currentTab === 0 ? imageTemplates : textTemplates; + + const renderTemplateList = templateList => { + if (templateList.length === 0) { + return {t('questions.template.noTemplates')}; + } + + return ( + + {templateList.map(template => ( + + + {template.question} + + {template.usageCount > 0 && ( + + )} + + } + secondary={template.description} + /> + + handleEdit(template)} sx={{ mr: 1 }}> + + + handleDelete(template.id)} disabled={template.usageCount > 0}> + + + + + ))} + + ); + }; + + return ( + <> + + + + {t('questions.template.management')} + + + + + + setCurrentTab(newValue)}> + + + + + {renderTemplateList(currentTemplates)} + + + + + + + setFormOpen(false)} + onSubmit={handleFormSubmit} + template={editingTemplate} + sourceType={currentTab === 0 ? 'image' : 'text'} + /> + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionDelete.js b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionDelete.js new file mode 100644 index 0000000..78de9af --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionDelete.js @@ -0,0 +1,125 @@ +'use client'; + +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; +import { toast } from 'sonner'; + +export function useQuestionDelete(projectId, onDeleteSuccess) { + const { t } = useTranslation(); + + // 确认对话框状态 + const [confirmDialog, setConfirmDialog] = useState({ + open: false, + title: '', + content: '', + confirmAction: null + }); + + // 执行单个问题删除 + const executeDeleteQuestion = async (questionId, selectedQuestions, setSelectedQuestions) => { + toast.promise(axios.delete(`/api/projects/${projectId}/questions/${questionId}`), { + loading: '数据删除中', + success: data => { + // 更新选中状态 + setSelectedQuestions(prev => (prev.includes(questionId) ? prev.filter(id => id !== questionId) : prev)); + // 调用成功回调 + if (onDeleteSuccess) { + onDeleteSuccess(); + } + return t('common.deleteSuccess'); + }, + error: error => { + return error.response?.data?.message || '删除失败'; + } + }); + }; + + // 确认删除单个问题 + const confirmDeleteQuestion = (questionId, selectedQuestions, setSelectedQuestions) => { + setConfirmDialog({ + open: true, + title: t('common.confirmDelete'), + content: t('common.confirmDeleteQuestion'), + confirmAction: () => executeDeleteQuestion(questionId, selectedQuestions, setSelectedQuestions) + }); + }; + + // 处理删除单个问题的入口函数 + const handleDeleteQuestion = (questionId, selectedQuestions, setSelectedQuestions) => { + confirmDeleteQuestion(questionId, selectedQuestions, setSelectedQuestions); + }; + + // 执行批量删除问题 + const executeBatchDeleteQuestions = async (selectedQuestions, setSelectedQuestions) => { + toast.promise( + axios.delete(`/api/projects/${projectId}/questions/batch-delete`, { + data: { questionIds: selectedQuestions } + }), + { + loading: `正在删除 ${selectedQuestions.length} 个问题...`, + success: data => { + // 调用成功回调 + if (onDeleteSuccess) { + onDeleteSuccess(); + } + // 清空选中状态 + setSelectedQuestions([]); + return `成功删除 ${selectedQuestions.length} 个问题`; + }, + error: error => { + return error.response?.data?.message || '批量删除问题失败'; + } + } + ); + }; + + // 确认批量删除问题 + const confirmBatchDeleteQuestions = (selectedQuestions, setSelectedQuestions) => { + if (selectedQuestions.length === 0) { + toast.warning('请先选择问题'); + return; + } + + setConfirmDialog({ + open: true, + title: '确认批量删除问题', + content: `您确定要删除选中的 ${selectedQuestions.length} 个问题吗?此操作不可恢复。`, + confirmAction: () => executeBatchDeleteQuestions(selectedQuestions, setSelectedQuestions) + }); + }; + + // 处理批量删除问题的入口函数 + const handleBatchDeleteQuestions = (selectedQuestions, setSelectedQuestions) => { + confirmBatchDeleteQuestions(selectedQuestions, setSelectedQuestions); + }; + + // 关闭确认对话框 + const closeConfirmDialog = () => { + setConfirmDialog({ + open: false, + title: '', + content: '', + confirmAction: null + }); + }; + + // 确认对话框的确认操作 + const handleConfirmAction = () => { + closeConfirmDialog(); + if (confirmDialog.confirmAction) { + confirmDialog.confirmAction(); + } + }; + + return { + // 状态 + confirmDialog, + + // 方法 + handleDeleteQuestion, + handleBatchDeleteQuestions, + closeConfirmDialog, + handleConfirmAction + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionEdit.js b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionEdit.js new file mode 100644 index 0000000..8759e9a --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionEdit.js @@ -0,0 +1,84 @@ +'use client'; + +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import request from '@/lib/util/request'; + +export function useQuestionEdit(projectId, onSuccess) { + const { t } = useTranslation(); + const [editDialogOpen, setEditDialogOpen] = useState(false); + const [editMode, setEditMode] = useState('create'); + const [editingQuestion, setEditingQuestion] = useState(null); + + const handleOpenCreateDialog = () => { + setEditMode('create'); + setEditingQuestion(null); + setEditDialogOpen(true); + }; + + const handleOpenEditDialog = question => { + setEditMode('edit'); + setEditingQuestion(question); + setEditDialogOpen(true); + }; + + const handleCloseDialog = () => { + setEditDialogOpen(false); + setEditingQuestion(null); + }; + + const handleSubmitQuestion = async formData => { + try { + const response = await request(`/api/projects/${projectId}/questions`, { + method: editMode === 'create' ? 'POST' : 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify( + editMode === 'create' + ? { + question: formData.question, + chunkId: formData.chunkId, + label: formData.label, + imageId: formData.imageId, + imageName: formData.imageName + } + : { + id: formData.id, + question: formData.question, + chunkId: formData.chunkId, + label: formData.label, + imageId: formData.imageId, + imageName: formData.imageName + } + ) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('questions.operationFailed')); + } + + // 获取更新后的问题数据 + const updatedQuestion = await response.json(); + + // 直接更新问题列表中的数据,而不是重新获取整个列表 + if (onSuccess) { + onSuccess(updatedQuestion); + } + handleCloseDialog(); + } catch (error) { + console.error('操作失败:', error); + } + }; + + return { + editDialogOpen, + editMode, + editingQuestion, + handleOpenCreateDialog, + handleOpenEditDialog, + handleCloseDialog, + handleSubmitQuestion + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionExport.js b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionExport.js new file mode 100644 index 0000000..f5b9a5b --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionExport.js @@ -0,0 +1,114 @@ +'use client'; + +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import axios from 'axios'; + +const useQuestionExport = projectId => { + const { t } = useTranslation(); + + // 导出问题集 + const exportQuestions = async exportOptions => { + try { + const apiUrl = `/api/projects/${projectId}/questions/export`; + const requestBody = { + format: exportOptions.format || 'json' + }; + + // 如果有选中的问题 ID,传递 ID 列表 + if (exportOptions.selectedIds && exportOptions.selectedIds.length > 0) { + requestBody.selectedIds = exportOptions.selectedIds; + } + + // 如果有筛选条件,传递筛选参数 + if (exportOptions.filters) { + requestBody.filters = exportOptions.filters; + } + + const response = await axios.post(apiUrl, requestBody); + const questions = response.data; + + // 处理和下载数据 + await processAndDownloadData(questions, exportOptions); + + toast.success(t('questions.exportSuccess')); + return true; + } catch (error) { + console.error('Export failed:', error); + toast.error(error.message || t('questions.exportFailed')); + return false; + } + }; + + // 处理和下载数据的通用函数 + const processAndDownloadData = async (data, exportOptions) => { + const format = exportOptions.format || 'json'; + let content; + let filename; + let mimeType; + + const timestamp = new Date().toISOString().split('T')[0]; + + switch (format) { + case 'json': + content = JSON.stringify(data, null, 2); + filename = `questions-${projectId}-${timestamp}.json`; + mimeType = 'application/json'; + break; + + case 'jsonl': + content = data.map(item => JSON.stringify(item)).join('\n'); + filename = `questions-${projectId}-${timestamp}.jsonl`; + mimeType = 'application/jsonl'; + break; + + case 'txt': + content = data.map(item => item.question).join('\n\n'); + filename = `questions-${projectId}-${timestamp}.txt`; + mimeType = 'text/plain'; + break; + + case 'csv': + // CSV 格式 + const headers = Object.keys(data[0] || {}); + const csvRows = [headers.join(',')]; + data.forEach(item => { + const values = headers.map(header => { + const value = item[header] || ''; + // 处理包含逗号或引号的值 + if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) { + return `"${value.replace(/"/g, '""')}"`; + } + return value; + }); + csvRows.push(values.join(',')); + }); + content = csvRows.join('\n'); + filename = `questions-${projectId}-${timestamp}.csv`; + mimeType = 'text/csv'; + break; + + default: + content = JSON.stringify(data, null, 2); + filename = `questions-${projectId}-${timestamp}.json`; + mimeType = 'application/json'; + } + + // 创建下载链接 + const blob = new Blob([content], { type: mimeType }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }; + + return { + exportQuestions + }; +}; + +export default useQuestionExport; diff --git a/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionGeneration.js b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionGeneration.js new file mode 100644 index 0000000..952bac3 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionGeneration.js @@ -0,0 +1,291 @@ +'use client'; + +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; +import axios from 'axios'; +import i18n from '@/lib/i18n'; +import request from '@/lib/util/request'; +import { processInParallel } from '@/lib/util/processInParallel'; + +export function useQuestionGeneration(projectId, model, taskSettings, getQuestionList) { + const { t } = useTranslation(); + + // 处理状态 + const [processing, setProcessing] = useState(false); + + // 进度状态 + const [progress, setProgress] = useState({ + total: 0, // 总共选择的问题数量 + completed: 0, // 已处理完成的数量 + percentage: 0, // 进度百分比 + datasetCount: 0 // 已生成的数据集数量 + }); + + // 批量生成答案 + const handleBatchGenerateAnswers = async selectedQuestions => { + if (selectedQuestions.length === 0) { + toast.warning(t('questions.noQuestionsSelected')); + return; + } + + if (!model) { + toast.warning(t('models.configNotFound')); + return; + } + + try { + setProgress({ + total: selectedQuestions.length, + completed: 0, + percentage: 0, + datasetCount: 0 + }); + + // 然后设置处理状态为真,确保进度条显示 + setProcessing(true); + + toast.info(t('questions.batchGenerateStart', { count: selectedQuestions.length })); + + // 单个问题处理函数 + const processQuestion = async questionId => { + try { + console.log('开始生成数据集:', { questionId }); + const language = i18n.language === 'zh-CN' ? '中文' : 'en'; + // 调用API生成数据集 + const response = await request(`/api/projects/${projectId}/datasets`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + questionId, + model, + language + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + console.error(t('datasets.generateError'), errorData.error || t('datasets.generateFailed')); + + // 更新进度状态(即使失败也计入已处理) + setProgress(prev => { + const completed = prev.completed + 1; + const percentage = Math.round((completed / prev.total) * 100); + + return { + ...prev, + completed, + percentage + }; + }); + + return { success: false, questionId, error: errorData.error || t('datasets.generateFailed') }; + } + + const data = await response.json(); + + // 更新进度状态 + setProgress(prev => { + const completed = prev.completed + 1; + const percentage = Math.round((completed / prev.total) * 100); + const datasetCount = prev.datasetCount + 1; + + return { + ...prev, + completed, + percentage, + datasetCount + }; + }); + + console.log(`数据集生成成功: ${questionId}`); + return { success: true, questionId, data: data.dataset }; + } catch (error) { + console.error('生成数据集失败:', error); + + // 更新进度状态(即使失败也计入已处理) + setProgress(prev => { + const completed = prev.completed + 1; + const percentage = Math.round((completed / prev.total) * 100); + + return { + ...prev, + completed, + percentage + }; + }); + + return { success: false, questionId, error: error.message }; + } + }; + + // 并行处理所有问题,最多同时处理2个 + const results = await processInParallel(selectedQuestions, processQuestion, taskSettings.concurrencyLimit); + + // 刷新数据 + getQuestionList(); + + // 处理完成后设置结果消息 + const successCount = results.filter(r => r.success).length; + const failCount = results.filter(r => !r.success).length; + + if (failCount > 0) { + toast.warning( + t('datasets.partialSuccess', { + successCount, + total: selectedQuestions.length, + failCount + }) + ); + } else { + toast.success(t('common.success', { successCount })); + } + } catch (error) { + console.error('生成数据集出错:', error); + toast.error(error.message || '生成数据集失败'); + } finally { + // 延迟关闭处理状态,确保用户可以看到完成的进度 + setTimeout(() => { + setProcessing(false); + // 再次延迟重置进度状态 + setTimeout(() => { + setProgress({ + total: 0, + completed: 0, + percentage: 0, + datasetCount: 0 + }); + }, 500); + }, 2000); // 延迟关闭处理状态,让用户看到完成的进度 + } + }; + + // 自动生成数据集 + const handleAutoGenerateDatasets = async () => { + try { + if (!model) { + toast.error(t('questions.selectModelFirst', { defaultValue: '请先选择模型' })); + return; + } + + // 调用创建任务接口 + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'answer-generation', + modelInfo: model, + language: i18n.language + }); + + if (response.data?.code === 0) { + toast.success(t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理未生成答案的问题' })); + } else { + toast.error(t('tasks.createFailed', { defaultValue: '创建后台任务失败' })); + } + } catch (error) { + console.error('创建任务失败:', error); + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message); + } + }; + + // 自动生成图像问答数据集 + const handleAutoGenerateImageDatasets = async () => { + try { + if (!model) { + toast.error(t('questions.selectModelFirst', { defaultValue: '请先选择模型' })); + return; + } + + if (model.type !== 'vision') { + toast.error(t('images.visionModelRequired', { defaultValue: '请选择支持视觉的模型' })); + return; + } + + // 调用创建任务接口 + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'image-dataset-generation', + modelInfo: model, + language: i18n.language + }); + + if (response.data?.code === 0) { + toast.success(t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理未生成答案的图片问题' })); + } else { + toast.error(t('tasks.createFailed', { defaultValue: '创建后台任务失败' })); + } + } catch (error) { + console.error('创建图片数据集任务失败:', error); + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message); + } + }; + + // 自动生成多轮对话数据集 + const handleAutoGenerateMultiTurnDatasets = async () => { + try { + if (!model) { + toast.error(t('questions.selectModelFirst', { defaultValue: '请先选择模型' })); + return; + } + + // 首先检查项目是否配置了多轮对话设置 + const configResponse = await axios.get(`/api/projects/${projectId}/tasks`); + if (configResponse.status !== 200) { + throw new Error('获取项目配置失败'); + } + + const config = configResponse.data; + const multiTurnConfig = { + systemPrompt: config.multiTurnSystemPrompt, + scenario: config.multiTurnScenario, + rounds: config.multiTurnRounds, + roleA: config.multiTurnRoleA, + roleB: config.multiTurnRoleB + }; + + // 检查是否已配置必要的多轮对话设置 + if ( + !multiTurnConfig.scenario || + !multiTurnConfig.roleA || + !multiTurnConfig.roleB || + !multiTurnConfig.rounds || + multiTurnConfig.rounds < 1 + ) { + toast.error(t('questions.multiTurnNotConfigured', '请先在项目设置中配置多轮对话相关参数')); + return; + } + + // 调用创建任务接口 + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'multi-turn-generation', + modelInfo: model, + language: i18n.language, + config: JSON.stringify(multiTurnConfig) + }); + + if (response.data?.code === 0) { + toast.success( + t('tasks.multiTurnCreateSuccess', { + defaultValue: '多轮对话生成任务已创建,系统将自动处理未生成多轮对话的问题' + }) + ); + } else { + toast.error(t('tasks.createFailed', { defaultValue: '创建后台任务失败' })); + } + } catch (error) { + console.error('创建多轮对话任务失败:', error); + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message); + } + }; + + return { + // 状态 + processing, + progress, + + // 方法 + handleBatchGenerateAnswers, + handleAutoGenerateDatasets, + handleAutoGenerateMultiTurnDatasets, + handleAutoGenerateImageDatasets + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionTemplates.js b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionTemplates.js new file mode 100644 index 0000000..414d99e --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionTemplates.js @@ -0,0 +1,122 @@ +import { useState, useEffect } from 'react'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; + +/** + * 问题模板管理 Hook + * @param {string} projectId - 项目ID + * @param {string} sourceType - 数据源类型: 'image' | 'text' | null (null表示获取所有) + */ +export function useQuestionTemplates(projectId, sourceType = null) { + const { t } = useTranslation(); + const [templates, setTemplates] = useState([]); + const [loading, setLoading] = useState(false); + + // 获取模板列表 + const fetchTemplates = async () => { + try { + setLoading(true); + const params = sourceType ? `?sourceType=${sourceType}` : ''; + const response = await axios.get(`/api/projects/${projectId}/questions/templates${params}`); + if (response.data.success) { + setTemplates(response.data.templates); + } + } catch (error) { + console.error('Failed to fetch templates:', error); + toast.error(t('questions.fetchTemplatesFailed')); + } finally { + setLoading(false); + } + }; + + // 创建模板 + const createTemplate = async data => { + try { + const response = await axios.post(`/api/projects/${projectId}/questions/templates`, data); + if (response.data.success) { + const { template, generation } = response.data; + + // 显示模板创建成功消息 + toast.success(t('questions.createTemplateSuccess')); + + // 如果有自动生成结果,显示相应消息 + if (generation) { + if (generation.success) { + if (generation.successCount > 0) { + toast.success( + t('questions.template.autoGenerateSuccess', { + count: generation.successCount + }) + ); + } + if (generation.failCount > 0) { + toast.warning( + t('questions.template.autoGeneratePartialFail', { + success: generation.successCount, + fail: generation.failCount + }) + ); + } + } else { + toast.error(generation.message || t('questions.template.autoGenerateFailed')); + } + } + + fetchTemplates(); + return template; + } + } catch (error) { + console.error('Failed to create template:', error); + toast.error(t('questions.createTemplateFailed')); + throw error; + } + }; + + // 更新模板 + const updateTemplate = async (templateId, data) => { + try { + const response = await axios.put(`/api/projects/${projectId}/questions/templates/${templateId}`, data); + if (response.data.success) { + toast.success(t('questions.updateTemplateSuccess')); + fetchTemplates(); + return response.data.template; + } + } catch (error) { + console.error('Failed to update template:', error); + toast.error(t('questions.updateTemplateFailed')); + throw error; + } + }; + + // 删除模板 + const deleteTemplate = async templateId => { + try { + const response = await axios.delete(`/api/projects/${projectId}/questions/templates/${templateId}`); + if (response.data.success) { + toast.success(t('questions.deleteTemplateSuccess')); + fetchTemplates(); + } + } catch (error) { + console.error('Failed to delete template:', error); + toast.error(t('questions.deleteTemplateFailed')); + throw error; + } + }; + + // 初始加载 + useEffect(() => { + if (projectId) { + fetchTemplates(); + } + }, [projectId, sourceType]); + + return { + templates, + loading, + createTemplate, + updateTemplate, + deleteTemplate, + refetch: fetchTemplates + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionsFilter.js b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionsFilter.js new file mode 100644 index 0000000..5f874cb --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/hooks/useQuestionsFilter.js @@ -0,0 +1,117 @@ +'use client'; + +import { useState } from 'react'; +import { useDebounce } from '@/hooks/useDebounce'; +import axios from 'axios'; + +export function useQuestionsFilter(projectId) { + // 过滤和搜索状态 + const [answerFilter, setAnswerFilter] = useState('all'); // 'all', 'answered', 'unanswered' + const [searchTerm, setSearchTerm] = useState(''); + const [searchMatchMode, setSearchMatchMode] = useState('match'); // 'match', 'notMatch' + const [chunkNameFilter, setChunkNameFilter] = useState(''); + const [sourceTypeFilter, setSourceTypeFilter] = useState('all'); // 'all', 'text', 'image' + const debouncedSearchTerm = useDebounce(searchTerm); + const debouncedChunkNameFilter = useDebounce(chunkNameFilter); + + // 选择状态 + const [selectedQuestions, setSelectedQuestions] = useState([]); + + // 处理问题选择 + const handleSelectQuestion = (questionKey, newSelected) => { + if (newSelected) { + // 处理批量选择的情况 + setSelectedQuestions(newSelected); + } else { + // 处理单个问题选择的情况 + setSelectedQuestions(prev => { + if (prev.includes(questionKey)) { + return prev.filter(id => id !== questionKey); + } else { + return [...prev, questionKey]; + } + }); + } + }; + + // 全选/取消全选 + const handleSelectAll = async () => { + if (selectedQuestions.length > 0) { + setSelectedQuestions([]); + } else { + const response = await axios.get( + `/api/projects/${projectId}/questions?status=${answerFilter}&input=${searchTerm}&searchMatchMode=${searchMatchMode}&chunkName=${encodeURIComponent(chunkNameFilter)}&sourceType=${sourceTypeFilter}&selectedAll=1` + ); + setSelectedQuestions(response.data.map(dataset => dataset.id)); + } + }; + + // 处理搜索输入变化 + const handleSearchChange = event => { + setSearchTerm(event.target.value); + }; + + // 处理过滤器变化 + const handleFilterChange = event => { + setAnswerFilter(event.target.value); + }; + + // 处理文本块名称筛选变化 + const handleChunkNameFilterChange = event => { + setChunkNameFilter(event.target.value); + }; + + // 处理数据源类型筛选变化 + const handleSourceTypeFilterChange = event => { + setSourceTypeFilter(event.target.value); + }; + + // 处理搜索匹配模式变化 + const handleSearchMatchModeChange = event => { + setSearchMatchMode(event.target.value); + }; + + // 清空选择 + const clearSelection = () => { + setSelectedQuestions([]); + }; + + // 重置所有过滤条件 + const resetFilters = () => { + setSearchTerm(''); + setSearchMatchMode('match'); + setAnswerFilter('all'); + setChunkNameFilter(''); + setSourceTypeFilter('all'); + setSelectedQuestions([]); + }; + + return { + // 状态 + answerFilter, + searchTerm, + debouncedSearchTerm, + searchMatchMode, + chunkNameFilter, + debouncedChunkNameFilter, + sourceTypeFilter, + selectedQuestions, + + // 方法 + setAnswerFilter, + setSearchTerm, + setSearchMatchMode, + setChunkNameFilter, + setSourceTypeFilter, + setSelectedQuestions, + handleSelectQuestion, + handleSelectAll, + handleSearchChange, + handleFilterChange, + handleChunkNameFilterChange, + handleSourceTypeFilterChange, + handleSearchMatchModeChange, + clearSelection, + resetFilters + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/questions/page.js b/easy-dataset-main/app/projects/[projectId]/questions/page.js new file mode 100644 index 0000000..199afbf --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/questions/page.js @@ -0,0 +1,416 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Container, Typography, Box, Paper, Tabs, Tab, CircularProgress, Divider, LinearProgress } from '@mui/material'; + +import QuestionListView from '@/components/questions/QuestionListView'; +import QuestionTreeView from '@/components/questions/QuestionTreeView'; +import TabPanel from '@/components/text-split/components/TabPanel'; +import useTaskSettings from '@/hooks/useTaskSettings'; +import QuestionEditDialog from './components/QuestionEditDialog'; +import QuestionsPageHeader from './components/QuestionsPageHeader'; +import ConfirmDialog from './components/ConfirmDialog'; +import TemplateListView from './components/TemplateListView'; +import TemplateFormDialog from './components/template/TemplateFormDialog'; +import ExportQuestionsDialog from './components/ExportQuestionsDialog'; +import { useQuestionTemplates } from './hooks/useQuestionTemplates'; +import { useQuestionEdit } from './hooks/useQuestionEdit'; +import { useQuestionDelete } from './hooks/useQuestionDelete'; +import { useQuestionsFilter } from './hooks/useQuestionsFilter'; +import QuestionsFilter from './components/QuestionsFilter'; +import { useQuestionGeneration } from './hooks/useQuestionGeneration'; +import useQuestionExport from './hooks/useQuestionExport'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useAtomValue } from 'jotai/index'; +import { selectedModelInfoAtom } from '@/lib/store'; + +export default function QuestionsPage({ params }) { + const { t } = useTranslation(); + const { projectId } = params; + const [loading, setLoading] = useState(true); + const [questions, setQuestions] = useState({}); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [tags, setTags] = useState([]); + const model = useAtomValue(selectedModelInfoAtom); + const [activeTab, setActiveTab] = useState(0); + + // 模板管理 + const { + templates, + loading: templatesLoading, + createTemplate, + updateTemplate, + deleteTemplate + } = useQuestionTemplates(projectId, null); // null 表示获取所有类型的模板 + + const [templateDialogOpen, setTemplateDialogOpen] = useState(false); + const [editingTemplate, setEditingTemplate] = useState(null); + const [exportDialogOpen, setExportDialogOpen] = useState(false); + + // 使用新的过滤和搜索 Hook + const { + answerFilter, + searchTerm, + debouncedSearchTerm, + searchMatchMode, + chunkNameFilter, + debouncedChunkNameFilter, + sourceTypeFilter, + selectedQuestions, + setSelectedQuestions, + handleSelectQuestion, + handleSelectAll, + handleSearchChange, + handleFilterChange, + handleChunkNameFilterChange, + handleSourceTypeFilterChange, + handleSearchMatchModeChange + } = useQuestionsFilter(projectId); + + const getQuestionList = async () => { + try { + // 获取问题列表 + const questionsResponse = await axios.get( + `/api/projects/${projectId}/questions?page=${currentPage}&size=10&status=${answerFilter}&input=${searchTerm}&searchMatchMode=${searchMatchMode}&chunkName=${encodeURIComponent(debouncedChunkNameFilter)}&sourceType=${sourceTypeFilter}` + ); + if (questionsResponse.status !== 200) { + throw new Error(t('common.fetchError')); + } + setQuestions(questionsResponse.data || {}); + + // 获取标签树 + const tagsResponse = await axios.get(`/api/projects/${projectId}/tags`); + if (tagsResponse.status !== 200) { + throw new Error(t('common.fetchError')); + } + setTags(tagsResponse.data.tags || []); + + setLoading(false); + } catch (error) { + console.error(t('common.fetchError'), error); + toast.error(error.message); + } + }; + + // 当筛选条件改变时,重置页码到第1页 + useEffect(() => { + setCurrentPage(1); + }, [answerFilter, debouncedSearchTerm, debouncedChunkNameFilter, sourceTypeFilter, searchMatchMode]); + + useEffect(() => { + getQuestionList(); + }, [currentPage, answerFilter, debouncedSearchTerm, debouncedChunkNameFilter, sourceTypeFilter, searchMatchMode]); + + const { taskSettings } = useTaskSettings(projectId); + + // 使用新的问题生成 Hook + const { + processing, + progress, + handleBatchGenerateAnswers, + handleAutoGenerateDatasets, + handleAutoGenerateMultiTurnDatasets, + handleAutoGenerateImageDatasets + } = useQuestionGeneration(projectId, model, taskSettings, getQuestionList); + + const { + editDialogOpen, + editMode, + editingQuestion, + handleOpenCreateDialog, + handleOpenEditDialog, + handleCloseDialog, + handleSubmitQuestion + } = useQuestionEdit(projectId, updatedQuestion => { + getQuestionList(); + toast.success(t('questions.operationSuccess')); + }); + + const { confirmDialog, handleDeleteQuestion, handleBatchDeleteQuestions, closeConfirmDialog, handleConfirmAction } = + useQuestionDelete(projectId, () => { + getQuestionList(); + }); + + const { exportQuestions } = useQuestionExport(projectId); + + // 获取所有数据 + useEffect(() => { + getQuestionList(); + }, [projectId]); + + // 处理标签页切换 + const handleTabChange = (event, newValue) => { + setActiveTab(newValue); + }; + + // 模板管理函数 + const handleOpenCreateTemplateDialog = () => { + setEditingTemplate(null); + setTemplateDialogOpen(true); + }; + + const handleEditTemplate = template => { + setEditingTemplate(template); + setTemplateDialogOpen(true); + }; + + const handleCloseTemplateDialog = () => { + setTemplateDialogOpen(false); + setEditingTemplate(null); + }; + + const handleSubmitTemplate = async data => { + try { + if (editingTemplate) { + await updateTemplate(editingTemplate.id, data); + } else { + await createTemplate(data); + } + getQuestionList(); + handleCloseTemplateDialog(); + } catch (error) { + console.error('Failed to save template:', error); + } + }; + + const handleDeleteTemplate = async templateId => { + const confirmed = window.confirm(t('questions.template.deleteConfirm')); + if (confirmed) { + try { + await deleteTemplate(templateId); + } catch (error) { + console.error('Failed to delete template:', error); + } + } + }; + + const handleOpenExportDialog = () => { + setExportDialogOpen(true); + }; + + const handleCloseExportDialog = () => { + setExportDialogOpen(false); + }; + + const handleExportQuestions = async exportOptions => { + const options = { + ...exportOptions, + selectedIds: selectedQuestions, + filters: { + searchTerm: debouncedSearchTerm, + chunkName: debouncedChunkNameFilter, + sourceType: sourceTypeFilter + } + }; + await exportQuestions(options); + }; + + if (loading) { + return ( + + + + + + ); + } + + return ( + + {/* 处理中的进度显示 - 全局蒙版样式 */} + {processing && ( + + + + {t('datasets.generatingDataset')} + + + + + + {progress.percentage}% + + + + + + + + + {t('questions.generatingProgress', { + completed: progress.completed, + total: progress.total + })} + + + {t('questions.generatedCount', { count: progress.datasetCount })} + + + + + + + + {t('questions.pleaseWait')} + + + + )} + + handleBatchDeleteQuestions(selectedQuestions, setSelectedQuestions)} + onOpenCreateDialog={handleOpenCreateDialog} + onOpenCreateTemplateDialog={handleOpenCreateTemplateDialog} + onBatchGenerateAnswers={() => handleBatchGenerateAnswers(selectedQuestions)} + onAutoGenerateDatasets={handleAutoGenerateDatasets} + onAutoGenerateMultiTurnDatasets={handleAutoGenerateMultiTurnDatasets} + onAutoGenerateImageDatasets={handleAutoGenerateImageDatasets} + onExportQuestions={handleOpenExportDialog} + /> + + + + + + + + + 0 && selectedQuestions.length === questions?.total} + isIndeterminate={selectedQuestions.length > 0 && selectedQuestions.length < questions?.total} + onSelectAll={handleSelectAll} + searchTerm={searchTerm} + onSearchChange={handleSearchChange} + searchMatchMode={searchMatchMode} + onSearchMatchModeChange={handleSearchMatchModeChange} + answerFilter={answerFilter} + onFilterChange={handleFilterChange} + chunkNameFilter={chunkNameFilter} + onChunkNameFilterChange={handleChunkNameFilterChange} + sourceTypeFilter={sourceTypeFilter} + onSourceTypeFilterChange={handleSourceTypeFilterChange} + activeTab={activeTab} + /> + + + + + setCurrentPage(newPage)} + selectedQuestions={selectedQuestions} + onSelectQuestion={handleSelectQuestion} + onDeleteQuestion={questionId => handleDeleteQuestion(questionId, selectedQuestions, setSelectedQuestions)} + onEditQuestion={handleOpenEditDialog} + refreshQuestions={getQuestionList} + projectId={projectId} + /> + + + + + + + + handleDeleteQuestion(questionId, selectedQuestions, setSelectedQuestions)} + onEditQuestion={handleOpenEditDialog} + projectId={projectId} + searchTerm={searchTerm} + /> + + + + {/* 确认对话框 */} + + + + + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/settings/components/CategoryTabs.js b/easy-dataset-main/app/projects/[projectId]/settings/components/CategoryTabs.js new file mode 100644 index 0000000..99aa6fe --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/settings/components/CategoryTabs.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { Tabs, Tab } from '@mui/material'; + +/** + * 顶部分类选择标签页组件 + */ +const CategoryTabs = ({ categoryEntries, selectedCategory, currentLanguage, onCategoryChange }) => { + return ( + { + onCategoryChange(newValue); + }} + variant="scrollable" + scrollButtons="auto" + sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }} + > + {categoryEntries.map(([categoryKey, categoryConfig]) => ( + + ))} + + ); +}; + +export default CategoryTabs; diff --git a/easy-dataset-main/app/projects/[projectId]/settings/components/PromptDetail.js b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptDetail.js new file mode 100644 index 0000000..9bac229 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptDetail.js @@ -0,0 +1,92 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Card, CardContent, Box, Typography, Chip, Button, Paper } from '@mui/material'; +import { Edit as EditIcon, Restore as RestoreIcon } from '@mui/icons-material'; +import ReactMarkdown from 'react-markdown'; + +import 'github-markdown-css/github-markdown-light.css'; + +/** + * 右侧提示词详情展示组件 + */ +const PromptDetail = ({ + currentPromptConfig, + selectedPrompt, + promptContent, + isCustomized, + onEditClick, + onDeleteClick +}) => { + const { t } = useTranslation(); + + if (!currentPromptConfig) { + return ( + {t('settings.prompts.selectPromptFirst')} + ); + } + + const handleEditClick = () => { + onEditClick(); + }; + + const handleDeleteClick = () => { + onDeleteClick(); + }; + + return ( + + + {/* 标题、描述与操作区域 */} + + + + {currentPromptConfig.name} + {isCustomized(selectedPrompt) && ( + + )} + + + + + + {isCustomized(selectedPrompt) && ( + + )} + + + + + {currentPromptConfig.description} + + + + {/* Markdown 渲染提示词内容 */} + +
+ {promptContent} +
+
+
+
+ ); +}; + +export default PromptDetail; diff --git a/easy-dataset-main/app/projects/[projectId]/settings/components/PromptEditDialog.js b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptEditDialog.js new file mode 100644 index 0000000..bfaa95a --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptEditDialog.js @@ -0,0 +1,71 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + Typography, + Chip +} from '@mui/material'; +import SaveIcon from '@mui/icons-material/Save'; +import RestoreIcon from '@mui/icons-material/Restore'; + +/** + * 提示词编辑对话框组件 + */ +const PromptEditDialog = ({ + open, + title, + promptType, + promptKey, + content, + loading, + onClose, + onSave, + onRestore, + onContentChange +}) => { + const { t } = useTranslation(); + return ( + + {title} + + + + {t('settings.prompts.promptType')}: {promptType} + + + {t('settings.prompts.keyName')}: {promptKey} + + + onContentChange(e.target.value)} + placeholder={t('settings.prompts.contentPlaceholder')} + variant="outlined" + /> + + + + + + + + + + + ); +}; + +export default PromptEditDialog; diff --git a/easy-dataset-main/app/projects/[projectId]/settings/components/PromptList.js b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptList.js new file mode 100644 index 0000000..84de7e7 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptList.js @@ -0,0 +1,81 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Box, Tabs, Tab, Typography, Chip } from '@mui/material'; +import { shouldShowPrompt } from './promptUtils'; + +/** + * 左侧提示词列表组件 + */ +const PromptList = ({ + currentCategory, + currentCategoryConfig, + selectedPrompt, + currentLanguage, + isCustomized, + onPromptSelect +}) => { + const { t } = useTranslation(); + + if (!currentCategoryConfig?.prompts) { + return ( + + {t('settings.prompts.noPromptsAvailable')} + + ); + } + + return ( + onPromptSelect(newValue)} + variant="scrollable" + scrollButtons="auto" + sx={{ + borderRight: 1, + borderColor: 'divider', + '& .MuiTabs-indicator': { + left: 0, + right: 'auto' + }, + '& .MuiTab-root': { + alignItems: 'flex-start', + textAlign: 'left' + } + }} + > + {currentCategoryConfig && + Object.entries(currentCategoryConfig.prompts).map(([promptKey, promptConfig]) => { + if (!shouldShowPrompt(promptKey, currentLanguage)) return null; + + const customized = isCustomized(promptKey); + + return ( + + + {promptConfig.name} + + {customized && ( + + )} + + } + sx={{ + alignItems: 'flex-start', + minHeight: 60, + px: 2, + justifyContent: 'flex-start', + width: '100%' + }} + /> + ); + })} + + ); +}; + +export default PromptList; diff --git a/easy-dataset-main/app/projects/[projectId]/settings/components/PromptSettings.js b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptSettings.js new file mode 100644 index 0000000..41a4715 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/settings/components/PromptSettings.js @@ -0,0 +1,400 @@ +import React, { useState, useEffect } from 'react'; +import { useParams } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; +import { Box, Grid, Card, CardContent } from '@mui/material'; +import { fetchWithRetry } from '@/lib/util/request'; +import { useSnackbar } from '@/hooks/useSnackbar'; + +// 导入拆分后的组件 +import CategoryTabs from './CategoryTabs'; +import PromptList from './PromptList'; +import PromptDetail from './PromptDetail'; +import PromptEditDialog from './PromptEditDialog'; +import { getLanguageFromPromptKey, shouldShowPrompt } from './promptUtils'; + +/** + * 提示词设置主组件 + */ +export default function PromptSettings() { + const { projectId } = useParams(); + const { i18n, t } = useTranslation(); + const { showSuccess, showErrorMessage, SnackbarComponent } = useSnackbar(); + + // 基础状态 + const [currentLanguage, setCurrentLanguage] = useState(i18n.language === 'en' ? 'en' : 'zh-CN'); + const [loading, setLoading] = useState(false); + const [templates, setTemplates] = useState({}); + const [customPrompts, setCustomPrompts] = useState([]); + + // 当前选中状态 + const [selectedCategory, setSelectedCategory] = useState(null); + const [selectedPrompt, setSelectedPrompt] = useState(null); + const [promptContent, setPromptContent] = useState(''); + + // 编辑对话框状态 + const [editDialog, setEditDialog] = useState({ + open: false, + promptType: '', + promptKey: '', + language: '', + content: '', + defaultContent: '', + isNew: false + }); + + // ======= 数据加载与初始化 ======= + + // 加载提示词数据 + useEffect(() => { + loadPromptData(); + }, [projectId, currentLanguage]); + + // 监听语言变化 + useEffect(() => { + const newLang = i18n.language === 'en' ? 'en' : 'zh-CN'; + if (newLang !== currentLanguage) { + setCurrentLanguage(newLang); + } + }, [i18n.language, currentLanguage]); + + // 监听选中提示词变化 + useEffect(() => { + if (selectedPrompt) { + loadPromptContent(); + } + }, [selectedPrompt]); + + // 初始化选择第一个分类和提示词 + useEffect(() => { + if (Object.keys(templates).length > 0 && currentLanguage && !selectedCategory) { + const firstCategory = Object.keys(templates)[0]; + setSelectedCategory(firstCategory); + + // 根据当前语言环境选择第一个匹配的提示词 + const promptEntries = Object.keys(templates[firstCategory]?.prompts || {}); + const firstPrompt = promptEntries.find(promptKey => shouldShowPrompt(promptKey, currentLanguage)); + + if (firstPrompt) { + setSelectedPrompt(firstPrompt); + } + } + }, [templates, selectedCategory, currentLanguage]); + + // ======= API 操作函数 ======= + + // 加载提示词数据 + const loadPromptData = async () => { + try { + setLoading(true); + const response = await fetchWithRetry(`/api/projects/${projectId}/custom-prompts?language=${currentLanguage}`); + const data = await response.json(); + + if (data.success) { + setTemplates(data.templates); + setCustomPrompts(data.customPrompts); + } else { + showErrorMessage(data.message || '加载提示词数据失败'); + } + } catch (error) { + console.error('加载提示词数据出错:', error); + showErrorMessage('加载提示词数据失败'); + } finally { + setLoading(false); + } + }; + + // 加载提示词内容 + const loadPromptContent = async (forceRefresh = false) => { + if (!selectedPrompt) return; + try { + setLoading(true); + const content = await getCurrentPromptContent(selectedPrompt, forceRefresh); + setPromptContent(content); + } catch (error) { + console.error('加载提示词内容出错:', error); + showErrorMessage('加载提示词内容失败'); + } finally { + setLoading(false); + } + }; + + // 加载默认提示词内容 + const loadDefaultContent = async (promptType, promptKey) => { + if (i18n.language === 'en' && !promptKey.endsWith('_EN')) { + promptKey += '_EN'; + } + try { + const response = await fetchWithRetry( + `/api/projects/${projectId}/default-prompts?promptType=${promptType}&promptKey=${promptKey}` + ); + const data = await response.json(); + + if (data.success) { + return data.content; + } + return ''; + } catch (error) { + console.error('加载默认提示词内容出错:', error); + return ''; + } + }; + + // ======= 交互处理函数 ======= + + // 处理编辑提示词 + const handleEditPrompt = async (promptType, promptKey, language) => { + const existingPrompt = customPrompts.find( + p => p.promptType === promptType && p.promptKey === promptKey && p.language === language + ); + + const defaultContent = await loadDefaultContent(promptType, promptKey); + + setEditDialog({ + open: true, + promptType, + promptKey, + language, + content: existingPrompt?.content || defaultContent, + defaultContent, + isNew: !existingPrompt + }); + }; + + // 处理删除提示词 + const handleDeletePrompt = async (promptType, promptKey, language) => { + try { + setLoading(true); + const query = new URLSearchParams({ + promptType, + promptKey, + language + }).toString(); + + const response = await fetchWithRetry(`/api/projects/${projectId}/custom-prompts?${query}`, { + method: 'DELETE' + }); + const data = await response.json(); + + if (data.success) { + showSuccess(t('settings.prompts.restoreSuccess')); + // 先重新加载数据,然后强制刷新内容 + await loadPromptData(); + await loadPromptContent(true); // 强制刷新 + } else { + showErrorMessage(data.message || t('settings.prompts.restoreFailed')); + } + } catch (error) { + console.error(t('settings.prompts.deleteError'), error); + showErrorMessage(t('settings.prompts.restoreFailed')); + } finally { + setLoading(false); + } + }; + + // 处理保存提示词 + const handleSavePrompt = async () => { + try { + setLoading(true); + const { promptType, promptKey, language, content } = editDialog; + + const response = await fetchWithRetry(`/api/projects/${projectId}/custom-prompts`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ promptType, promptKey, language, content }) + }); + const data = await response.json(); + + if (data.success) { + showSuccess(t('settings.prompts.saveSuccess')); + setEditDialog({ ...editDialog, open: false }); + // 先重新加载数据,然后强制刷新内容 + await loadPromptData(); + await loadPromptContent(true); // 强制刷新 + } else { + showErrorMessage(data.message || t('settings.prompts.saveFailed')); + } + } catch (error) { + console.error(t('settings.prompts.saveError'), error); + showErrorMessage(t('settings.prompts.saveFailed')); + } finally { + setLoading(false); + } + }; + + // 恢复默认内容 + const handleRestoreDefault = () => { + setEditDialog(prev => ({ + ...prev, + content: prev.defaultContent + })); + }; + + // ======= 工具函数 ======= + + // 检查提示词是否已自定义 + const isCustomized = promptKey => { + if (!selectedCategory || !promptKey || !templates[selectedCategory]) return false; + + const language = getLanguageFromPromptKey(promptKey); + const promptType = templates[selectedCategory]?.prompts?.[promptKey]?.type; + + if (!promptType) return false; + + return customPrompts.some(p => p.promptType === promptType && p.promptKey === promptKey && p.language === language); + }; + + // 获取当前提示词内容(直接从服务器获取最新数据) + const getCurrentPromptContent = async (promptKey, forceRefresh = false) => { + if (!selectedCategory || !promptKey || !templates[selectedCategory]) return ''; + + const language = getLanguageFromPromptKey(promptKey); + const promptType = templates[selectedCategory]?.prompts?.[promptKey]?.type; + + if (!promptType) { + return ''; + } + + // 如果需要强制刷新,直接从服务器获取 + if (forceRefresh) { + try { + const response = await fetchWithRetry( + `/api/projects/${projectId}/custom-prompts?promptType=${promptType}&language=${language}` + ); + const data = await response.json(); + + if (data.success) { + const existingPrompt = data.customPrompts.find( + p => p.promptType === promptType && p.promptKey === promptKey && p.language === language + ); + + if (existingPrompt) { + return existingPrompt.content; + } + } + } catch (error) { + console.error(t('settings.prompts.fetchContentError'), error); + } + } else { + // 使用缓存的状态 + const existingPrompt = customPrompts.find( + p => p.promptType === promptType && p.promptKey === promptKey && p.language === language + ); + + if (existingPrompt) { + return existingPrompt.content; + } + } + + // 回退到默认内容 + return await loadDefaultContent(promptType, promptKey); + }; + + // ======= 数据准备 ======= + + // 当前分类的配置 + const currentCategoryConfig = templates[selectedCategory]; + + // 当前提示词的配置 + const currentPromptConfig = currentCategoryConfig?.prompts?.[selectedPrompt]; + + // 分类配置项 + const categoryEntries = Object.entries(templates); + + // 处理分类变更 + const handleCategoryChange = newCategory => { + setSelectedCategory(newCategory); + // 根据当前语言环境选择第一个匹配的提示词 + const promptEntries = Object.keys(templates[newCategory]?.prompts || {}); + console.log('所有提示词:', promptEntries); + + const firstPrompt = promptEntries.find(promptKey => shouldShowPrompt(promptKey, currentLanguage)); + + setSelectedPrompt(firstPrompt); + }; + + // 处理编辑按钮点击 + const handleEditButtonClick = () => { + const promptType = templates[selectedCategory]?.prompts?.[selectedPrompt]?.type; + // 使用当前界面语言而不是从 promptKey 推断的语言 + const language = currentLanguage; + + if (promptType) { + handleEditPrompt(promptType, selectedPrompt, language); + } + }; + + // 处理删除按钮点击 + const handleDeleteButtonClick = () => { + const promptType = templates[selectedCategory]?.prompts?.[selectedPrompt]?.type; + // 使用当前界面语言而不是从 promptKey 推断的语言 + const language = currentLanguage; + + if (promptType) { + handleDeletePrompt(promptType, selectedPrompt, language); + } + }; + + // 处理对话框内容变更 + const handleDialogContentChange = newContent => { + setEditDialog({ ...editDialog, content: newContent }); + }; + + return ( + + + + {/* 主要分类选择 */} + + + {/* 左右布局:左侧垂直提示词选择,右侧内容展示 */} + + {/* 左侧:垂直 TAB 选择具体提示词 */} + + + + + + + + + {/* 右侧:提示词内容展示和操作 */} + + + + + + {/* 编辑提示词对话框 */} + setEditDialog({ ...editDialog, open: false })} + onSave={handleSavePrompt} + onRestore={handleRestoreDefault} + onContentChange={handleDialogContentChange} + /> + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/settings/components/promptUtils.js b/easy-dataset-main/app/projects/[projectId]/settings/components/promptUtils.js new file mode 100644 index 0000000..b188de4 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/settings/components/promptUtils.js @@ -0,0 +1,34 @@ +/** + * 提示词设置相关工具函数 + */ + +/** + * 从提示词键名解析语言 + * @param {string} promptKey 提示词键名 + * @returns {string} 语言代码 ('zh-CN' 或 'en') + */ +export const getLanguageFromPromptKey = promptKey => { + return promptKey?.endsWith('_EN') ? 'en' : 'zh-CN'; +}; + +/** + * 判断是否应该显示当前提示词(基于语言) + * @param {string} promptKey 提示词键名 + * @param {string} currentLanguage 当前界面语言 + * @returns {boolean} 是否应该显示 + */ +export const shouldShowPrompt = (promptKey, currentLanguage) => { + const promptLang = getLanguageFromPromptKey(promptKey); + return promptLang === currentLanguage; +}; + +/** + * 构建提示词标题显示组件 + * @param {Object} options 配置项 + * @param {string} options.name 提示词名称 + * @param {boolean} options.customized 是否已自定义 + * @returns {Object} 包含名称和自定义标记的显示配置 + */ +export const buildPromptTitle = ({ name, customized }) => { + return { name, customized }; +}; diff --git a/easy-dataset-main/app/projects/[projectId]/settings/page.js b/easy-dataset-main/app/projects/[projectId]/settings/page.js new file mode 100644 index 0000000..ebd193a --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/settings/page.js @@ -0,0 +1,125 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Container, Typography, Box, Tabs, Tab, Paper, Alert, CircularProgress } from '@mui/material'; +import { useSearchParams, useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; + +// 导入设置组件 +import BasicSettings from '@/components/settings/BasicSettings'; +import ModelSettings from '@/components/settings/ModelSettings'; +import TaskSettings from '@/components/settings/TaskSettings'; +import PromptSettings from './components/PromptSettings'; + +// 定义 TAB 枚举 +const TABS = { + BASIC: 'basic', + MODEL: 'model', + TASK: 'task', + PROMPTS: 'prompts' +}; + +export default function SettingsPage({ params }) { + const { t } = useTranslation(); + const { projectId } = params; + const searchParams = useSearchParams(); + const router = useRouter(); + const [activeTab, setActiveTab] = useState(TABS.BASIC); + const [projectExists, setProjectExists] = useState(true); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + // 从 URL hash 中获取当前 tab + useEffect(() => { + const tab = searchParams.get('tab'); + if (tab && Object.values(TABS).includes(tab)) { + setActiveTab(tab); + } + }, [searchParams]); + + // 检查项目是否存在 + useEffect(() => { + async function checkProject() { + try { + setLoading(true); + const response = await fetch(`/api/projects/${projectId}`); + + if (!response.ok) { + if (response.status === 404) { + setProjectExists(false); + } else { + throw new Error(t('projects.fetchFailed')); + } + } else { + setProjectExists(true); + } + } catch (error) { + console.error('获取项目详情出错:', error); + setError(error.message); + } finally { + setLoading(false); + } + } + + checkProject(); + }, [projectId, t]); + + // 处理 tab 切换 + const handleTabChange = (event, newValue) => { + setActiveTab(newValue); + // 更新 URL hash + router.push(`/projects/${projectId}/settings?tab=${newValue}`); + }; + + if (loading) { + return ( + + + + ); + } + + if (!projectExists) { + return ( + + {t('projects.notExist')} + + ); + } + + if (error) { + return ( + + {error} + + ); + } + + return ( + + + + + + + + + + + {activeTab === TABS.BASIC && } + + {activeTab === TABS.MODEL && } + + {activeTab === TABS.TASK && } + + {activeTab === TABS.PROMPTS && } + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/tasks/page.js b/easy-dataset-main/app/projects/[projectId]/tasks/page.js new file mode 100644 index 0000000..5933377 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/tasks/page.js @@ -0,0 +1,175 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Box, Typography, Container, LinearProgress, Paper } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; +import TaskIcon from '@mui/icons-material/Task'; +import { toast } from 'sonner'; + +import TaskFilters from '@/components/tasks/TaskFilters'; +import TasksTable from '@/components/tasks/TasksTable'; + +export default function TasksPage({ params }) { + const { projectId } = params; + const { t } = useTranslation(); + + const [loading, setLoading] = useState(false); + const [tasks, setTasks] = useState([]); + const [statusFilter, setStatusFilter] = useState('all'); + const [typeFilter, setTypeFilter] = useState('all'); + const [page, setPage] = useState(0); + const [rowsPerPage, setRowsPerPage] = useState(10); + const [totalCount, setTotalCount] = useState(0); + + const processingTasks = tasks.filter(task => task.status === 0 && task.totalCount > 0); + const totalProgressCount = processingTasks.reduce((sum, task) => sum + task.totalCount, 0); + const completedProgressCount = processingTasks.reduce((sum, task) => sum + task.completedCount, 0); + const overallProgress = totalProgressCount > 0 ? Math.round((completedProgressCount / totalProgressCount) * 100) : 0; + + const fetchTasks = async () => { + if (!projectId) return; + + try { + setLoading(true); + let url = `/api/projects/${projectId}/tasks/list`; + const queryParams = []; + + if (statusFilter !== 'all') { + queryParams.push(`status=${statusFilter}`); + } + + if (typeFilter !== 'all') { + queryParams.push(`taskType=${typeFilter}`); + } + + queryParams.push(`page=${page}`); + queryParams.push(`limit=${rowsPerPage}`); + + if (queryParams.length > 0) { + url += `?${queryParams.join('&')}`; + } + + const response = await axios.get(url); + if (response.data?.code === 0) { + setTasks(response.data.data || []); + setTotalCount(response.data.total || response.data.data?.length || 0); + } + } catch (error) { + console.error('Failed to fetch tasks:', error); + toast.error(t('tasks.fetchFailed')); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchTasks(); + + const intervalId = setInterval(() => { + if (statusFilter === 'all' || statusFilter === '0') { + fetchTasks(); + } + }, 5000); + + return () => clearInterval(intervalId); + }, [projectId, statusFilter, typeFilter, page, rowsPerPage]); + + const handleDeleteTask = async taskId => { + if (!confirm(t('tasks.confirmDelete'))) return; + + try { + const response = await axios.delete(`/api/projects/${projectId}/tasks/${taskId}`); + if (response.data?.code === 0) { + toast.success(t('tasks.deleteSuccess')); + fetchTasks(); + } else { + toast.error(t('tasks.deleteFailed')); + } + } catch (error) { + console.error('Failed to delete task:', error); + toast.error(t('tasks.deleteFailed')); + } + }; + + const handleAbortTask = async taskId => { + if (!confirm(t('tasks.confirmAbort'))) return; + + try { + const response = await axios.patch(`/api/projects/${projectId}/tasks/${taskId}`, { + status: 3, + note: t('tasks.status.aborted') + }); + + if (response.data?.code === 0) { + toast.success(t('tasks.abortSuccess')); + fetchTasks(); + } else { + toast.error(t('tasks.abortFailed')); + } + } catch (error) { + console.error('Failed to abort task:', error); + toast.error(t('tasks.abortFailed')); + } + }; + + const handleChangePage = (event, newPage) => { + setPage(newPage); + }; + + const handleChangeRowsPerPage = event => { + setRowsPerPage(parseInt(event.target.value, 10)); + setPage(0); + }; + + return ( + + + + + {t('tasks.title')} + + + + + + {processingTasks.length > 0 && ( + + + {t('tasks.pending', { count: processingTasks.length })} - {completedProgressCount}/{totalProgressCount} ( + {overallProgress}%) + + + + )} + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/text-split/page.js b/easy-dataset-main/app/projects/[projectId]/text-split/page.js new file mode 100644 index 0000000..a30c640 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/text-split/page.js @@ -0,0 +1,440 @@ +'use client'; + +import axios from 'axios'; +import { useState, useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Container, + Box, + Tabs, + Tab, + IconButton, + Collapse, + Dialog, + DialogContent, + DialogTitle, + Typography, + LinearProgress, + CircularProgress +} from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import FullscreenIcon from '@mui/icons-material/Fullscreen'; +import CloseIcon from '@mui/icons-material/Close'; +import FileUploader from '@/components/text-split/FileUploader'; +import FileList from '@/components/text-split/components/FileList'; +import DeleteConfirmDialog from '@/components/text-split/components/DeleteConfirmDialog'; +import PdfSettings from '@/components/text-split/PdfSettings'; +import ChunkList from '@/components/text-split/ChunkList'; +import DomainAnalysis from '@/components/text-split/DomainAnalysis'; +import useTaskSettings from '@/hooks/useTaskSettings'; +import { useAtomValue } from 'jotai/index'; +import { selectedModelInfoAtom } from '@/lib/store'; +import useChunks from './useChunks'; +import useQuestionGeneration from './useQuestionGeneration'; +import useDataCleaning from './useDataCleaning'; +import useEvalGeneration from './useEvalGeneration'; +import useFileProcessing from './useFileProcessing'; +import useFileProcessingStatus from '@/hooks/useFileProcessingStatus'; +import { toast } from 'sonner'; + +export default function TextSplitPage({ params }) { + const { t } = useTranslation(); + const theme = useTheme(); + const { projectId } = params; + const [activeTab, setActiveTab] = useState(0); + const [renderedTab, setRenderedTab] = useState(0); + const [tabSwitching, setTabSwitching] = useState(false); + const tabSwitchTimerRef = useRef(null); + const { taskSettings } = useTaskSettings(projectId); + const [pdfStrategy, setPdfStrategy] = useState('default'); + const [questionFilter, setQuestionFilter] = useState('all'); // 'all', 'generated', 'ungenerated' + const [selectedViosnModel, setSelectedViosnModel] = useState(''); + const selectedModelInfo = useAtomValue(selectedModelInfoAtom); + const { taskFileProcessing, task } = useFileProcessingStatus(); + const [currentPage, setCurrentPage] = useState(1); + const [uploadedFiles, setUploadedFiles] = useState({ data: [], total: 0 }); + const [searchFileName, setSearchFileName] = useState(''); + const [showLoadingBar, setShowLoadingBar] = useState(false); + + // 娑撳﹣绱堕崠鍝勭厵閻ㄥ嫬鐫嶅鈧?閹舵ê褰旈悩鑸碘偓? + const [uploaderExpanded, setUploaderExpanded] = useState(true); + + // 閺傚洨灏為崚妤勩€?FileList)鐏炴洜銇氱€电鐦藉鍡欏Ц閹? + const [fileListDialogOpen, setFileListDialogOpen] = useState(false); + + // 娴h法鏁ら懛顏勭暰娑斿“ooks + const { chunks, tocData, loading, fetchChunks, handleDeleteChunk, handleEditChunk, updateChunks, setLoading } = + useChunks(projectId, questionFilter); + + // 閼惧嘲褰囬弬鍥︽閸掓銆? + const fetchUploadedFiles = async (page = currentPage, fileName = searchFileName) => { + try { + setLoading(true); + const params = new URLSearchParams({ + page: page.toString(), + size: '10' + }); + + if (fileName && fileName.trim()) { + params.append('fileName', fileName.trim()); + } + + const response = await axios.get(`/api/projects/${projectId}/files?${params}`); + setUploadedFiles(response.data); + } catch (error) { + console.error('Error fetching files:', error); + toast.error(error.message || '閼惧嘲褰囬弬鍥︽閸掓銆冩径杈Е'); + } finally { + setLoading(false); + } + }; + + // 閸掔娀娅庨弬鍥︽绾喛顓荤€电鐦藉鍡欏Ц閹? + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [fileToDelete, setFileToDelete] = useState(null); + + // 閹垫挸绱戦崚鐘绘珟绾喛顓荤€电鐦藉? + const openDeleteConfirm = (fileId, fileName) => { + setFileToDelete({ fileId, fileName }); + setDeleteConfirmOpen(true); + }; + + // 閸忔娊妫撮崚鐘绘珟绾喛顓荤€电鐦藉? + const closeDeleteConfirm = () => { + setDeleteConfirmOpen(false); + setFileToDelete(null); + }; + + // 绾喛顓婚崚鐘绘珟閺傚洣娆? + const confirmDeleteFile = async () => { + if (!fileToDelete) return; + + try { + setLoading(true); + closeDeleteConfirm(); + + await axios.delete(`/api/projects/${projectId}/files/${fileToDelete.fileId}`); + await fetchUploadedFiles(); + fetchChunks(); + + toast.success( + t('textSplit.deleteSuccess', { fileName: fileToDelete.fileName }) || `删除 ${fileToDelete.fileName} 成功` + ); + } catch (error) { + console.error('删除文件出错:', error); + toast.error(error.message || '删除文件失败'); + } finally { + setLoading(false); + setFileToDelete(null); + } + }; + + const { handleGenerateQuestions } = useQuestionGeneration(projectId, taskSettings); + const { handleDataCleaning } = useDataCleaning(projectId, taskSettings); + const { handleGenerateEvalQuestions } = useEvalGeneration(projectId); + const { handleFileProcessing } = useFileProcessing(projectId); + + // 文本块数据刷新:初始化 + 文件处理任务状态变化 + useEffect(() => { + fetchChunks('all'); + }, [fetchChunks, taskFileProcessing]); + + // 文件列表刷新:文件分页、搜索关键词变化时触发 + useEffect(() => { + fetchUploadedFiles(currentPage, searchFileName); + }, [projectId, currentPage, searchFileName]); + + useEffect(() => { + let timerId; + if (loading) { + timerId = setTimeout(() => setShowLoadingBar(true), 180); + } else { + setShowLoadingBar(false); + } + return () => { + if (timerId) clearTimeout(timerId); + }; + }, [loading]); + + useEffect(() => { + return () => { + if (tabSwitchTimerRef.current) { + clearTimeout(tabSwitchTimerRef.current); + } + }; + }, []); + + const handleTabChange = (event, newValue) => { + if (newValue === activeTab) return; + + setActiveTab(newValue); + setTabSwitching(true); + + if (tabSwitchTimerRef.current) { + clearTimeout(tabSwitchTimerRef.current); + } + + const switchContent = () => { + setRenderedTab(newValue); + tabSwitchTimerRef.current = null; + if (typeof window !== 'undefined') { + window.requestAnimationFrame(() => setTabSwitching(false)); + } else { + setTabSwitching(false); + } + }; + + if (typeof window !== 'undefined') { + window.requestAnimationFrame(() => { + tabSwitchTimerRef.current = setTimeout(switchContent, 80); + }); + } else { + switchContent(); + } + }; + + /** + * 鐎甸€涚瑐娴肩姴鎮楅惃鍕瀮娴犳儼绻樼悰灞筋槱閻? + */ + const handleUploadSuccess = async (fileNames, pdfFiles, domainTreeAction) => { + try { + await handleFileProcessing(fileNames, pdfStrategy, selectedViosnModel, domainTreeAction); + location.reload(); + } catch (error) { + toast.error('File upload failed' + error.message || ''); + } + }; + + // 閸栧懓顥婇悽鐔稿灇闂傤噣顣介惃鍕槱閻炲棗鍤遍弫? + const onGenerateQuestions = async chunkIds => { + await handleGenerateQuestions(chunkIds, selectedModelInfo, fetchChunks); + }; + + // 閸栧懓顥婇弫鐗堝祦濞撳懏绀傞惃鍕槱閻炲棗鍤遍弫? + const onDataCleaning = async chunkIds => { + await handleDataCleaning(chunkIds, selectedModelInfo, fetchChunks); + }; + + // 閸栧懓顥婇悽鐔稿灇濞村鐦庢0妯兼窗閻ㄥ嫬顦╅悶鍡楀毐閺? + const onGenerateEvalQuestions = async chunkId => { + await handleGenerateEvalQuestions(chunkId, selectedModelInfo, () => { + // 閹存劕濮涢崥搴″煕閺傛澘鍨悰? + fetchChunks(); + }); + }; + + useEffect(() => { + const url = new URL(window.location.href); + if (questionFilter !== 'all') { + url.searchParams.set('filter', questionFilter); + } else { + url.searchParams.delete('filter'); + } + window.history.replaceState({}, '', url); + fetchChunks(questionFilter); + }, [questionFilter]); + + const handleSelected = array => { + if (array.length > 0) { + axios.post(`/api/projects/${projectId}/chunks`, { array }).then(response => { + updateChunks(response.data); + }); + } else { + fetchChunks(); + } + }; + + return ( + + {/* 閺傚洣娆㈡稉濠佺炊缂佸嫪娆?*/} + + + setUploaderExpanded(!uploaderExpanded)} + sx={{ + bgcolor: 'background.paper', + boxShadow: 1, + mr: uploaderExpanded ? 1 : 0 // 鐏炴洖绱戦弮鑸靛瘻闁筋喕绠i梻瀵告殌閻愬綊妫跨捄? + }} + size="small" + > + {uploaderExpanded ? : } + + + {/* 閺傚洨灏為崚妤勩€冮幍鈺佺潔閹稿鎸抽敍灞肩矌閸︺劋绗傞柈銊ュ隘閸╃喎鐫嶅鈧弮鑸垫▔缁€?*/} + {uploaderExpanded && ( + setFileListDialogOpen(true)} + sx={{ bgcolor: 'background.paper', boxShadow: 1 }} + size="small" + title={t('textSplit.expandFileList') || '扩展文件列表'} + > + + + )} + + + + + + + + + {/* 閺嶅洨顒锋い?*/} + + + + + + + + + {/* 閺呴缚鍏橀崚鍡楀閺嶅洨顒烽崘鍛啇 */} + {tabSwitching ? ( + + + + {t('common.loading')} + + + ) : ( + <> + {renderedTab === 0 && ( + + )} + + {renderedTab === 1 && } + + )} + + + {/* 閸旂姾娴囨稉顓℃寢閻?*/} + {showLoadingBar && ( + + + + {t('textSplit.loading')} + + + + + )} + + {/* 婢跺嫮鎮婃稉顓℃寢閻?*/} + + {/* 閺佺増宓佸〒鍛鏉╂稑瀹抽拏娆戝 */} + + {/* 閺傚洣娆㈡径鍕倞鏉╂稑瀹抽拏娆戝 */} + + {/* 閺傚洣娆㈤崚鐘绘珟绾喛顓荤€电鐦藉?*/} + + + {/* 閺傚洨灏為崚妤勩€冪€电鐦藉?*/} + setFileListDialogOpen(false)} + maxWidth="lg" + fullWidth + sx={{ '& .MuiDialog-paper': { bgcolor: 'background.default' } }} + > + + {t('textSplit.fileList')} + setFileListDialogOpen(false)} aria-label="close"> + + + + + {/* 濮濄倕顦╂径宥囨暏 FileUploader 缂佸嫪娆㈡稉顓犳畱 FileList 闁劌鍨?*/} + + {/* 閺傚洣娆㈤崚妤勩€冮崘鍛啇 */} + handleSelected(array)} + onDeleteFile={(fileId, fileName) => openDeleteConfirm(fileId, fileName)} + projectId={projectId} + currentPage={currentPage} + onPageChange={(page, fileName) => { + if (fileName !== undefined) { + // 閹兼粎鍌ㄩ弮鑸垫纯閺傜増鎮崇槐銏犲彠闁款喛鐦濋崪宀勩€夐惍? + setSearchFileName(fileName); + setCurrentPage(page); + } else { + // 缂堝銆夐弮璺哄涧閺囧瓨鏌婃い鐢电垳 + setCurrentPage(page); + } + }} + onRefresh={fetchUploadedFiles} // 娴肩娀鈧帒鍩涢弬鏉垮毐閺? + isFullscreen={true} // 閸︺劌顕拠婵囶攱娑擃厾些闂勩倝鐝惔锕傛閸? + /> + + + + + ); +} diff --git a/easy-dataset-main/app/projects/[projectId]/text-split/useChunks.js b/easy-dataset-main/app/projects/[projectId]/text-split/useChunks.js new file mode 100644 index 0000000..81a5b1f --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/text-split/useChunks.js @@ -0,0 +1,162 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { toast } from 'sonner'; + +/** + * 文本块管理的自定义Hook + * @param {string} projectId - 项目ID + * @param {string} [currentFilter='all'] - 当前筛选条件 + * @returns {Object} - 文本块状态和操作方法 + */ +export default function useChunks(projectId, currentFilter = 'all') { + const { t } = useTranslation(); + const [chunks, setChunks] = useState([]); + const [tocData, setTocData] = useState(''); + const [loading, setLoading] = useState(false); + + /** + * 获取文本块列表 + * @param {string} filter - 筛选条件 + */ + const fetchChunks = useCallback( + async (filter = 'all') => { + try { + setLoading(true); + const response = await fetch(`/api/projects/${projectId}/split?filter=${filter}`); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('textSplit.fetchChunksFailed')); + } + + const data = await response.json(); + setChunks(data.chunks || []); + + // 如果有文件结果,处理详细信息 + if (data.toc) { + console.log(t('textSplit.fileResultReceived'), data.fileResult); + // 如果有目录结构,设置目录数据 + setTocData(data.toc); + } + } catch (error) { + toast.error(error.message); + } finally { + setLoading(false); + } + }, + [projectId, t, setLoading, setChunks, setTocData] + ); + + /** + * 处理删除文本块 + * @param {string} chunkId - 文本块ID + */ + const handleDeleteChunk = useCallback( + async chunkId => { + try { + const response = await fetch(`/api/projects/${projectId}/chunks/${encodeURIComponent(chunkId)}`, { + method: 'DELETE' + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('textSplit.deleteChunkFailed')); + } + + // 更新文本块列表 + setChunks(prev => prev.filter(chunk => chunk.id !== chunkId)); + } catch (error) { + toast.error(error.message); + } + }, + [projectId, t] + ); + + /** + * 处理文本块编辑 + * @param {string} chunkId - 文本块ID + * @param {string} newContent - 新内容 + */ + const handleEditChunk = useCallback( + async (chunkId, newContent) => { + try { + setLoading(true); + + const response = await fetch(`/api/projects/${projectId}/chunks/${encodeURIComponent(chunkId)}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ content: newContent }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('textSplit.editChunkFailed')); + } + + // 更新成功后使用当前筛选条件刷新文本块列表 + // 直接从 URL 获取当前筛选参数,确保获取到的是最新的值 + const url = new URL(window.location.href); + const filterParam = url.searchParams.get('filter') || 'all'; + await fetchChunks(filterParam); + + toast.success(t('textSplit.editChunkSuccess')); + } catch (error) { + toast.error(error.message); + } finally { + setLoading(false); + } + }, + [projectId, t, fetchChunks] + ); + + /** + * 设置文本块列表 + * @param {Array} data - 新的文本块列表 + */ + const updateChunks = useCallback(data => { + setChunks(data); + }, []); + + /** + * 添加新的文本块 + * @param {Array} newChunks - 新的文本块列表 + */ + const addChunks = useCallback(newChunks => { + setChunks(prev => { + const updatedChunks = [...prev]; + newChunks.forEach(chunk => { + if (!updatedChunks.find(c => c.id === chunk.id)) { + updatedChunks.push(chunk); + } + }); + return updatedChunks; + }); + }, []); + + /** + * 设置TOC数据 + * @param {string} toc - TOC数据 + */ + const updateTocData = useCallback(toc => { + if (toc) { + setTocData(toc); + } + }, []); + + return { + chunks, + tocData, + loading, + fetchChunks, + handleDeleteChunk, + handleEditChunk, + updateChunks, + addChunks, + updateTocData, + setLoading + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/text-split/useDataCleaning.js b/easy-dataset-main/app/projects/[projectId]/text-split/useDataCleaning.js new file mode 100644 index 0000000..2d4daa5 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/text-split/useDataCleaning.js @@ -0,0 +1,116 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import i18n from '@/lib/i18n'; +import request from '@/lib/util/request'; +import { toast } from 'sonner'; + +export default function useDataCleaning(projectId) { + const { t } = useTranslation(); + const [processing, setProcessing] = useState(false); + const [progress, setProgress] = useState({ + total: 0, + completed: 0, + percentage: 0, + cleanedCount: 0 + }); + + const resetProgress = useCallback(() => { + setTimeout(() => { + setProgress({ + total: 0, + completed: 0, + percentage: 0, + cleanedCount: 0 + }); + }, 500); + }, []); + + const handleDataCleaning = useCallback( + async (chunkIds, selectedModelInfo, fetchChunks) => { + try { + if (!chunkIds || chunkIds.length === 0) return; + + if (!selectedModelInfo) { + throw new Error(t('textSplit.selectModelFirst')); + } + + setProcessing(true); + + if (chunkIds.length === 1) { + const chunkId = chunkIds[0]; + const currentLanguage = i18n.language === 'zh-CN' ? '中文' : 'en'; + + const response = await request(`/api/projects/${projectId}/chunks/${chunkId}/clean`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: selectedModelInfo, + language: currentLanguage + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('textSplit.dataCleaningFailed', { chunkId })); + } + + const data = await response.json(); + toast.success( + t('textSplit.dataCleaningSuccess', { + originalLength: data.originalLength, + cleanedLength: data.cleanedLength + }) + ); + + if (fetchChunks) fetchChunks(); + return; + } + + const response = await request(`/api/projects/${projectId}/tasks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + taskType: 'data-cleaning', + modelInfo: selectedModelInfo, + language: i18n.language, + detail: '批量数据清洗任务', + note: { chunkIds } + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('tasks.createFailed')); + } + + const data = await response.json(); + if (data?.code !== 0) { + throw new Error(data?.message || t('tasks.createFailed')); + } + + toast.success(`${t('tasks.createSuccess')},${t('tasks.title')}查看进度`); + } catch (error) { + toast.error(error.message); + } finally { + setProcessing(false); + resetProgress(); + } + }, + [projectId, t, resetProgress] + ); + + return { + processing, + progress, + setProgress, + setProcessing, + handleDataCleaning, + resetProgress + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/text-split/useEvalGeneration.js b/easy-dataset-main/app/projects/[projectId]/text-split/useEvalGeneration.js new file mode 100644 index 0000000..4ae9928 --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/text-split/useEvalGeneration.js @@ -0,0 +1,91 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import i18n from '@/lib/i18n'; +import request from '@/lib/util/request'; +import { toast } from 'sonner'; + +/** + * 测评题目生成的自定义Hook + * @param {string} projectId - 项目ID + * @returns {Object} - 测评题目生成状态和操作方法 + */ +export default function useEvalGeneration(projectId) { + const { t } = useTranslation(); + const [generating, setGenerating] = useState({}); + + /** + * 为单个文本块生成测评题目 + * @param {string} chunkId - 文本块ID + * @param {Object} selectedModelInfo - 选定的模型信息 + * @param {Function} onSuccess - 成功回调 + */ + const handleGenerateEvalQuestions = useCallback( + async (chunkId, selectedModelInfo, onSuccess) => { + try { + // 检查模型信息 + if (!selectedModelInfo) { + throw new Error(t('textSplit.selectModelFirst')); + } + + // 设置生成状态 + setGenerating(prev => ({ ...prev, [chunkId]: true })); + + // 获取当前语言环境 + const currentLanguage = i18n.language === 'zh-CN' ? 'zh-CN' : 'en'; + + // 调用API生成测评题目 + const response = await request( + `/api/projects/${projectId}/chunks/${encodeURIComponent(chunkId)}/eval-questions`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: selectedModelInfo, + language: currentLanguage + }) + } + ); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('textSplit.generateEvalQuestionsFailed')); + } + + const data = await response.json(); + + // 显示成功消息 + toast.success( + t('textSplit.evalQuestionsGeneratedSuccess', { + total: data.total, + defaultValue: `成功生成 ${data.total} 道测评题目` + }) + ); + + // 调用成功回调 + if (onSuccess) { + onSuccess(data); + } + } catch (error) { + console.error('Error generating eval questions:', error); + toast.error(error.message || t('textSplit.generateEvalQuestionsFailed')); + } finally { + // 清除生成状态 + setGenerating(prev => { + const newState = { ...prev }; + delete newState[chunkId]; + return newState; + }); + } + }, + [projectId, t] + ); + + return { + generating, + handleGenerateEvalQuestions + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/text-split/useFileProcessing.js b/easy-dataset-main/app/projects/[projectId]/text-split/useFileProcessing.js new file mode 100644 index 0000000..20bd5df --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/text-split/useFileProcessing.js @@ -0,0 +1,91 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import { selectedModelInfoAtom } from '@/lib/store'; +import { useAtomValue } from 'jotai/index'; +import { toast } from 'sonner'; +import i18n from '@/lib/i18n'; +import axios from 'axios'; + +/** + * 文件处理的自定义Hook + * @param {string} projectId - 项目ID + * @returns {Object} - 文件处理状态和操作方法 + */ +export default function useFileProcessing(projectId) { + const { t } = useTranslation(); + const [fileProcessing, setFileProcessing] = useState(false); + const [progress, setProgress] = useState({ + total: 0, + completed: 0, + percentage: 0, + questionCount: 0 + }); + const model = useAtomValue(selectedModelInfoAtom); + + /** + * 重置进度状态 + */ + const resetProgress = useCallback(() => { + setTimeout(() => { + setProgress({ + total: 0, + completed: 0, + percentage: 0, + questionCount: 0 + }); + }, 1000); // 延迟重置,让用户看到完成的进度 + }, []); + + /** + * 处理文件 + * @param {Array} files - 文件列表 + * @param {string} pdfStrategy - PDF处理策略 + * @param {string} selectedViosnModel - 选定的视觉模型 + */ + const handleFileProcessing = useCallback( + async (files, pdfStrategy, selectedViosnModel, domainTreeAction) => { + try { + const currentLanguage = i18n.language === 'zh-CN' ? '中文' : 'en'; + + //获取到视觉策略要使用的模型 + const availableModels = JSON.parse(localStorage.getItem('modelConfigList')); + const vsionModel = availableModels.find(m => m.id === selectedViosnModel); + + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'file-processing', + modelInfo: model, + language: currentLanguage, + detail: '文件处理任务', + note: { + vsionModel, + projectId, + fileList: files, + strategy: pdfStrategy, + domainTreeAction + } + }); + + if (response.data?.code !== 0) { + throw new Error(t('textSplit.pdfProcessingFailed') + (response.data?.error || '')); + } + + //提示后台任务进行中 + toast.success(t('textSplit.pdfProcessingToast')); + } catch (error) { + toast.error(t('textSplit.pdfProcessingFailed') + error.message || ''); + } + }, + [projectId, t, resetProgress, model] + ); + + return { + fileProcessing, + progress, + setFileProcessing, + setProgress, + handleFileProcessing, + resetProgress + }; +} diff --git a/easy-dataset-main/app/projects/[projectId]/text-split/useQuestionGeneration.js b/easy-dataset-main/app/projects/[projectId]/text-split/useQuestionGeneration.js new file mode 100644 index 0000000..8daee2e --- /dev/null +++ b/easy-dataset-main/app/projects/[projectId]/text-split/useQuestionGeneration.js @@ -0,0 +1,116 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; +import i18n from '@/lib/i18n'; +import request from '@/lib/util/request'; +import { toast } from 'sonner'; + +export default function useQuestionGeneration(projectId) { + const { t } = useTranslation(); + const [processing, setProcessing] = useState(false); + const [progress, setProgress] = useState({ + total: 0, + completed: 0, + percentage: 0, + questionCount: 0 + }); + + const resetProgress = useCallback(() => { + setTimeout(() => { + setProgress({ + total: 0, + completed: 0, + percentage: 0, + questionCount: 0 + }); + }, 500); + }, []); + + const handleGenerateQuestions = useCallback( + async (chunkIds, selectedModelInfo, fetchChunks) => { + try { + if (!chunkIds || chunkIds.length === 0) return; + + if (!selectedModelInfo) { + throw new Error(t('textSplit.selectModelFirst')); + } + + setProcessing(true); + + if (chunkIds.length === 1) { + const chunkId = chunkIds[0]; + const currentLanguage = i18n.language === 'zh-CN' ? '中文' : 'en'; + + const response = await request(`/api/projects/${projectId}/chunks/${chunkId}/questions`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: selectedModelInfo, + language: currentLanguage, + enableGaExpansion: true + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('textSplit.generateQuestionsFailed', { chunkId })); + } + + const data = await response.json(); + toast.success( + t('textSplit.questionsGeneratedSuccess', { + total: data.total + }) + ); + + if (fetchChunks) fetchChunks(); + return; + } + + const response = await request(`/api/projects/${projectId}/tasks`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + taskType: 'question-generation', + modelInfo: selectedModelInfo, + language: i18n.language, + detail: '批量生成问题任务', + note: { chunkIds } + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('tasks.createFailed')); + } + + const data = await response.json(); + if (data?.code !== 0) { + throw new Error(data?.message || t('tasks.createFailed')); + } + + toast.success(`${t('tasks.createSuccess')},${t('tasks.title')}查看进度`); + } catch (error) { + toast.error(error.message); + } finally { + setProcessing(false); + resetProgress(); + } + }, + [projectId, t, resetProgress] + ); + + return { + processing, + progress, + setProgress, + setProcessing, + handleGenerateQuestions, + resetProgress + }; +} diff --git a/easy-dataset-main/commitlint.config.mjs b/easy-dataset-main/commitlint.config.mjs new file mode 100644 index 0000000..3f5e287 --- /dev/null +++ b/easy-dataset-main/commitlint.config.mjs @@ -0,0 +1 @@ +export default { extends: ['@commitlint/config-conventional'] }; diff --git a/easy-dataset-main/components/ExportDatasetDialog.js b/easy-dataset-main/components/ExportDatasetDialog.js new file mode 100644 index 0000000..88639a5 --- /dev/null +++ b/easy-dataset-main/components/ExportDatasetDialog.js @@ -0,0 +1,226 @@ +// ExportDatasetDialog.js 组件 +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Box, Tabs, Tab } from '@mui/material'; + +// 导入拆分后的组件 +import LocalExportTab from './export/LocalExportTab'; +import LlamaFactoryTab from './export/LlamaFactoryTab'; +import HuggingFaceTab from './export/HuggingFaceTab'; + +const ExportDatasetDialog = ({ open, onClose, onExport, projectId }) => { + const { t } = useTranslation(); + const [formatType, setFormatType] = useState('alpaca'); + const [systemPrompt, setSystemPrompt] = useState(''); + const [reasoningLanguage, setReasoningLanguage] = useState(''); + const [confirmedOnly, setConfirmedOnly] = useState(false); + const [fileFormat, setFileFormat] = useState('json'); + const [includeCOT, setIncludeCOT] = useState(true); + const [currentTab, setCurrentTab] = useState(0); + // alpaca 格式特有的设置 + const [alpacaFieldType, setAlpacaFieldType] = useState('instruction'); // 'instruction' 或 'input' + const [customInstruction, setCustomInstruction] = useState(''); // 当选择 input 时使用的自定义 instruction + const [customFields, setCustomFields] = useState({ + questionField: 'instruction', + answerField: 'output', + cotField: 'complexCOT', // 添加思维链字段名 + includeLabels: false, + includeChunk: false, // 添加是否包含chunk字段 + questionOnly: false // 添加仅导出问题选项 + }); + + const handleFileFormatChange = event => { + setFileFormat(event.target.value); + }; + + const handleFormatChange = event => { + setFormatType(event.target.value); + // 根据格式类型设置默认字段名 + if (event.target.value === 'alpaca') { + setCustomFields({ + ...customFields, + questionField: 'instruction', + answerField: 'output' + }); + } else if (event.target.value === 'sharegpt') { + setCustomFields({ + ...customFields, + questionField: 'content', + answerField: 'content' + }); + } else if (event.target.value === 'multilingual-thinking') { + setCustomFields({ + ...customFields, + questionField: 'content', + answerField: 'content' + }); + } else if (event.target.value === 'custom') { + // 自定义格式保持当前值 + } + }; + + const handleSystemPromptChange = event => { + setSystemPrompt(event.target.value); + }; + + const handleReasoningLanguageChange = event => { + setReasoningLanguage(event.target.value); + }; + const handleConfirmedOnlyChange = event => { + setConfirmedOnly(event.target.checked); + }; + + // 新增处理函数 + const handleIncludeCOTChange = event => { + setIncludeCOT(event.target.checked); + }; + + const handleCustomFieldChange = field => event => { + setCustomFields({ + ...customFields, + [field]: event.target.value + }); + }; + + const handleIncludeLabelsChange = event => { + setCustomFields({ + ...customFields, + includeLabels: event.target.checked + }); + }; + + const handleIncludeChunkChange = event => { + setCustomFields({ + ...customFields, + includeChunk: event.target.checked + }); + }; + + const handleQuestionOnlyChange = event => { + setCustomFields({ + ...customFields, + questionOnly: event.target.checked + }); + }; + + // 处理 Alpaca 字段类型变更 + const handleAlpacaFieldTypeChange = event => { + setAlpacaFieldType(event.target.value); + }; + + // 处理自定义 instruction 变更 + const handleCustomInstructionChange = event => { + setCustomInstruction(event.target.value); + }; + + const handleExport = options => { + // 如果 LocalExportTab 传入了完整的导出配置(例如平衡导出),直接使用该配置 + if (options && typeof options === 'object' && options.balanceMode) { + onExport(options); + return; + } + + // 否则使用当前对话框内的状态组装导出配置 + onExport({ + formatType, + systemPrompt, + reasoningLanguage, + confirmedOnly, + fileFormat, + includeCOT, + alpacaFieldType, // 添加 alpaca 字段类型 + customInstruction, // 添加自定义 instruction + customFields: formatType === 'custom' ? customFields : undefined + }); + }; + + return ( + + {t('export.title')} + + + setCurrentTab(newValue)} aria-label="export tabs"> + + + + + + + {/* 第一个标签页:本地导出 */} + {currentTab === 0 && ( + + )} + + {/* 第二个标签页:Llama Factory */} + {currentTab === 1 && ( + + )} + + {/* 第三个标签页:HuggingFace */} + {currentTab === 2 && ( + + )} + + + ); +}; + +export default ExportDatasetDialog; diff --git a/easy-dataset-main/components/ExportProgressDialog.js b/easy-dataset-main/components/ExportProgressDialog.js new file mode 100644 index 0000000..db80bfc --- /dev/null +++ b/easy-dataset-main/components/ExportProgressDialog.js @@ -0,0 +1,104 @@ +'use client'; + +import React from 'react'; +import { Dialog, DialogTitle, DialogContent, Box, LinearProgress, Typography, CircularProgress } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +const ExportProgressDialog = ({ open, progress }) => { + const { t } = useTranslation(); + + const { processed, total, hasMore } = progress; + + // 计算进度百分比 + const percentage = total > 0 ? Math.round((processed / total) * 100) : 0; + + return ( + + {t('datasets.exportProgress')} + + + + {/* 圆形进度指示器 */} + + + + + {`${percentage}%`} + + + + + {/* 进度详情 */} + + + {t('datasets.exportingData')} + + + + {t('datasets.processedCount', { processed, total })} + + + {/* 线性进度条 */} + + + + {/* 状态提示 */} + + {hasMore ? t('datasets.exportInProgress') : t('datasets.exportFinalizing')} + + + + + ); +}; + +export default ExportProgressDialog; diff --git a/easy-dataset-main/components/I18nProvider.js b/easy-dataset-main/components/I18nProvider.js new file mode 100644 index 0000000..d253537 --- /dev/null +++ b/easy-dataset-main/components/I18nProvider.js @@ -0,0 +1,16 @@ +'use client'; + +import { useEffect } from 'react'; +import i18n from '@/lib/i18n'; +import { I18nextProvider } from 'react-i18next'; + +export default function I18nProvider({ children }) { + useEffect(() => { + // 确保i18n只在客户端初始化 + if (typeof window !== 'undefined') { + // 这里可以添加任何客户端特定的i18n初始化逻辑 + } + }, []); + + return {children}; +} diff --git a/easy-dataset-main/components/LanguageSwitcher.js b/easy-dataset-main/components/LanguageSwitcher.js new file mode 100644 index 0000000..3336a5b --- /dev/null +++ b/easy-dataset-main/components/LanguageSwitcher.js @@ -0,0 +1,88 @@ +'use client'; + +import { useTranslation } from 'react-i18next'; +import { IconButton, Menu, MenuItem, Tooltip, useTheme, Typography } from '@mui/material'; +import { useState } from 'react'; +import TranslateIcon from '@mui/icons-material/Translate'; + +export default function LanguageSwitcher() { + const { i18n, t } = useTranslation(); + const theme = useTheme(); + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + const languages = [ + { code: 'en', label: t('language.english', 'English'), short: 'EN' }, + { code: 'zh-CN', label: t('language.chineseSimplified', '简体中文'), short: '中文' }, + { code: 'tr', label: t('language.turkish', 'Türkçe'), short: 'TR' }, + { code: 'pt-BR', label: t('language.portugues', 'Portugues'), short: 'pt-BR' } + ]; + + const normalizedCurrentLanguage = + i18n.language && String(i18n.language).toLowerCase().startsWith('zh') ? 'zh-CN' : i18n.language; + const currentLanguage = languages.find(lang => lang.code === normalizedCurrentLanguage) || languages[0]; + + const handleClick = event => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; + + const handleLanguageChange = langCode => { + i18n.changeLanguage(langCode); + handleClose(); + }; + + return ( + <> + + + + {currentLanguage.short} + + + + + + {languages.map(lang => ( + handleLanguageChange(lang.code)} + selected={normalizedCurrentLanguage === lang.code} + > + + {lang.short} + + {lang.label} + + ))} + + + ); +} diff --git a/easy-dataset-main/components/ModelSelect.js b/easy-dataset-main/components/ModelSelect.js new file mode 100644 index 0000000..e554a85 --- /dev/null +++ b/easy-dataset-main/components/ModelSelect.js @@ -0,0 +1,346 @@ +'use client'; + +import React, { useEffect, useState, useMemo } from 'react'; +import { FormControl, Select, MenuItem, useTheme, ListSubheader, Box, IconButton, Tooltip } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useAtom, useAtomValue } from 'jotai/index'; +import { modelConfigListAtom, selectedModelInfoAtom } from '@/lib/store'; +import axios from 'axios'; +import SmartToyIcon from '@mui/icons-material/SmartToy'; +import { getModelIcon } from '@/lib/util/modelIcon'; + +export default function ModelSelect({ + size = 'small', + minWidth = 50, + projectId, + minHeight = 36, + required = false, + onError +}) { + const theme = useTheme(); + const { t } = useTranslation(); + const models = useAtomValue(modelConfigListAtom); + const [selectedModelInfo, setSelectedModelInfo] = useAtom(selectedModelInfoAtom); + const [selectedModel, setSelectedModel] = useState(() => { + if (selectedModelInfo && selectedModelInfo.id) { + return selectedModelInfo.id; + } + return ''; + }); + const [error, setError] = useState(false); + const [isHovered, setIsHovered] = useState(false); + const [isOpen, setIsOpen] = useState(false); + + const handleModelChange = event => { + if (!event || !event.target) return; + const newModelId = event.target.value; + + if (error) { + setError(false); + if (onError) onError(false); + } + + if (!newModelId) { + setSelectedModel(''); + setSelectedModelInfo(null); + updateDefaultModel(null); + } else { + const selectedModelObj = models.find(model => model.id === newModelId); + if (selectedModelObj) { + setSelectedModel(newModelId); + setSelectedModelInfo(selectedModelObj); + updateDefaultModel(newModelId); + } else { + setSelectedModel(newModelId); + setSelectedModelInfo({ id: newModelId }); + } + } + + setTimeout(() => { + setIsHovered(false); + setIsOpen(false); + }, 200); + }; + + const updateDefaultModel = async id => { + const res = await axios.put(`/api/projects/${projectId}`, { projectId, defaultModelConfigId: id }); + if (res.status === 200) { + console.log('更新成功'); + } + }; + + const validateModel = () => { + if (required && (!selectedModel || selectedModel === '')) { + setError(true); + if (onError) onError(true); + return false; + } + return true; + }; + + useEffect(() => { + if (selectedModelInfo && selectedModelInfo.id) { + setSelectedModel(selectedModelInfo.id); + } else { + setSelectedModel(''); + } + }, [selectedModelInfo]); + + useEffect(() => { + if (required) { + validateModel(); + } + }, [required]); + + const renderSelectedValue = value => { + if (!value) { + return ( + + + {t('models.unselectedModel', t('playground.selectModelFirst'))} + + ); + } + + const selectedModelObj = models.find(model => model.id === value); + if (!selectedModelObj) return null; + + return ( + + { + e.target.src = '/imgs/models/default.svg'; + }} + /> + {selectedModelObj.modelName} + + ); + }; + + const currentModelIcon = useMemo(() => { + const selectedModelObj = models.find(model => model.id === selectedModel); + return selectedModelObj ? getModelIcon(selectedModelObj.modelName, selectedModelObj.modelId) : null; + }, [selectedModel, models]); + + const shouldShowFullSelect = isHovered || isOpen; + + return ( + setIsHovered(true)} + onMouseLeave={() => { + setIsHovered(false); + if (!isOpen) { + setIsOpen(false); + } + }} + sx={{ + position: 'relative', + display: 'flex', + alignItems: 'center' + }} + > + {!shouldShowFullSelect && ( + m.id === selectedModel)?.modelName + : t('playground.selectModelFirst', '请先选择模型') + } + placement="bottom" + > + + {currentModelIcon ? ( + { + e.target.src = '/imgs/models/default.svg'; + }} + /> + ) : ( + + )} + + + )} + + + + + + ); +} diff --git a/easy-dataset-main/components/Navbar/ActionButtons.js b/easy-dataset-main/components/Navbar/ActionButtons.js new file mode 100644 index 0000000..5184a9d --- /dev/null +++ b/easy-dataset-main/components/Navbar/ActionButtons.js @@ -0,0 +1,112 @@ +'use client'; + +import React from 'react'; +import { Box, IconButton, Tooltip } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import LightModeOutlinedIcon from '@mui/icons-material/LightModeOutlined'; +import DarkModeOutlinedIcon from '@mui/icons-material/DarkModeOutlined'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import BarChartIcon from '@mui/icons-material/BarChart'; +import LanguageSwitcher from '../LanguageSwitcher'; +import UpdateChecker from '../UpdateChecker'; +import TaskIcon from '../TaskIcon'; +import ModelSelect from '../ModelSelect'; +import * as styles from './styles'; + +/** + * ActionButtons 组件 + * 右侧操作区按钮:语言切换、主题切换、文档、GitHub、更新检查 + */ +export default function ActionButtons({ + theme, + resolvedTheme, + toggleTheme, + isProjectDetail, + currentProject, + onActionAreaEnter +}) { + const { t, i18n } = useTranslation(); + const isZhLanguage = String(i18n.language || '') + .toLowerCase() + .startsWith('zh'); + + return ( + + {isProjectDetail && } + {isProjectDetail && } + + {/* Monitoring Dashboard - Only visible on Home page */} + {!isProjectDetail && ( + + + + + + )} + + {/* Language Switcher - Always visible */} + + + {/* Theme Toggle - Always visible */} + + + {resolvedTheme === 'dark' ? ( + + ) : ( + + )} + + + + {/* Documentation - Hide below xl */} + + + + + + + {/* GitHub - Hide at larger tablet screens and below */} + + + + + + + {/* Update Checker - Hide below xl */} + + + + + ); +} diff --git a/easy-dataset-main/components/Navbar/ContextBar.js b/easy-dataset-main/components/Navbar/ContextBar.js new file mode 100644 index 0000000..196f1d3 --- /dev/null +++ b/easy-dataset-main/components/Navbar/ContextBar.js @@ -0,0 +1,175 @@ +'use client'; + +import React, { useState } from 'react'; +import { + Box, + Chip, + Typography, + useTheme, + Menu, + MenuItem, + ListItemIcon, + ListItemText, + Paper, + Tooltip +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/navigation'; +import { useSetAtom } from 'jotai'; +import { modelConfigListAtom, selectedModelInfoAtom } from '@/lib/store'; +import { toast } from 'sonner'; +import axios from 'axios'; + +// Icons +import FolderIcon from '@mui/icons-material/Folder'; +import CheckIcon from '@mui/icons-material/Check'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; + +// 样式 +import * as styles from './contextBarStyles'; + +export default function ContextBar({ projects = [], currentProjectId, onMouseLeave }) { + const { t } = useTranslation(); + const theme = useTheme(); + const router = useRouter(); + + // State + const [projectMenuAnchor, setProjectMenuAnchor] = useState(null); + + // Jotai atoms + const setConfigList = useSetAtom(modelConfigListAtom); + const setSelectedModelInfo = useSetAtom(selectedModelInfoAtom); + + // Get current project + const currentProject = projects.find(p => p.id === currentProjectId); + + // Handlers + const handleProjectMenuOpen = event => { + event.preventDefault(); + setProjectMenuAnchor(event.currentTarget); + }; + + const handleProjectMenuClose = () => { + setProjectMenuAnchor(null); + // 菜单关闭时,如果提供了 onMouseLeave 回调,则调用它 + if (onMouseLeave) { + onMouseLeave(); + } + }; + + const handleProjectChange = async newProjectId => { + handleProjectMenuClose(); + + try { + // Fetch model config for new project + const response = await axios.get(`/api/projects/${newProjectId}/model-config`); + setConfigList(response.data.data); + + if (response.data.defaultModelConfigId) { + const defaultModel = response.data.data.find(item => item.id === response.data.defaultModelConfigId); + setSelectedModelInfo(defaultModel || null); + } else { + setSelectedModelInfo(null); + } + + // Navigate to the new project's text-split page + router.push(`/projects/${newProjectId}/text-split`); + } catch (error) { + console.error('Error switching project:', error); + toast.error(t('common.error', 'Error switching project')); + } + }; + + if (!currentProjectId || !currentProject) { + return null; + } + + return ( + + + {/* Project Selector */} + + + {t('common.project', 'Project')}: + + + } + label={ + + + {currentProject?.name || t('projects.selectProject', 'Select Project')} + + + + } + onClick={handleProjectMenuOpen} + clickable + variant="outlined" + size="medium" + sx={styles.getProjectChipStyles(theme)} + aria-label={t('projects.selectProject', 'Select project')} + aria-controls={projectMenuAnchor ? 'project-menu' : undefined} + aria-haspopup="true" + aria-expanded={Boolean(projectMenuAnchor)} + /> + + + + + {/* Project Menu */} + + + {t('projects.allProjects', 'All Projects')} + + {projects.map((project, index) => ( + handleProjectChange(project.id)} + selected={project.id === currentProjectId} + role="menuitem" + sx={styles.getMenuItemStyles(theme)} + > + + {project.id === currentProjectId ? ( + + ) : ( + + )} + + + + ))} + + + ); +} diff --git a/easy-dataset-main/components/Navbar/DesktopMenus.js b/easy-dataset-main/components/Navbar/DesktopMenus.js new file mode 100644 index 0000000..2f0f967 --- /dev/null +++ b/easy-dataset-main/components/Navbar/DesktopMenus.js @@ -0,0 +1,315 @@ +'use client'; + +import React from 'react'; +import { Menu, MenuItem, ListItemIcon, ListItemText, Divider } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import Link from 'next/link'; +import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'; +import ImageIcon from '@mui/icons-material/Image'; +import DatasetOutlinedIcon from '@mui/icons-material/DatasetOutlined'; +import ChatIcon from '@mui/icons-material/Chat'; +import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'; +import ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined'; +import StorageIcon from '@mui/icons-material/Storage'; +import AssessmentOutlinedIcon from '@mui/icons-material/AssessmentOutlined'; +import PlaylistPlayIcon from '@mui/icons-material/PlaylistPlay'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import * as styles from './styles'; + +/** + * DesktopMenus 缂備礁瀚▎? + * 婵℃鐭傚鎵博椤栨稑浜鹃柛瀣矎瑜板秹宕¢弴顏嗙闁告牕鎳庨幆鍫ュ极閻楀牆绁︽繝褎鍔戦埀顑跨劍閺嗙喖骞戦鈧▔锔剧不閿涘嫭鍊為柕鍡曠劍濞叉寧寰勫顐ょ憦濞戞搩浜hぐ宥夊础? + */ +export default function DesktopMenus({ + theme, + menuState, + isMenuOpen, + handleMenuClose, + currentProject, + onNavigateStart +}) { + const { t } = useTranslation(); + + return ( + <> + {/* 闁轰胶澧楀畵浣糕攦閹邦垰缍呴柛?*/} + + { + onNavigateStart?.(); + handleMenuClose(); + }} + role="menuitem" + sx={styles.getMenuItemStyles(theme)} + > + + + + + + + { + onNavigateStart?.(); + handleMenuClose(); + }} + role="menuitem" + sx={styles.getMenuItemStyles(theme)} + > + + + + + + + + {/* 闁轰胶澧楀畵渚€姊块崱娆樺悁闁荤偛妫滆ぐ宥夊础?*/} + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + + {/* 閻犲洤瀚崣濠囨嚕濠婂啫绀?*/} + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + + {/* 闁哄洦娼欓ˇ鍧楁嚕濠婂啫绀?*/} + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.getSimpleMenuItemStyles(theme)} + > + + + + + + + + ); +} diff --git a/easy-dataset-main/components/Navbar/Logo.js b/easy-dataset-main/components/Navbar/Logo.js new file mode 100644 index 0000000..7fc8e30 --- /dev/null +++ b/easy-dataset-main/components/Navbar/Logo.js @@ -0,0 +1,42 @@ +'use client'; + +import React from 'react'; +import { Box, Typography, Tooltip } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import * as styles from './styles'; + +/** + * Logo 组件 + * 显示应用 Logo 和标题,支持点击跳转到首页 + */ +export default function Logo({ theme }) { + const { t } = useTranslation(); + + return ( + + { + e.preventDefault(); + window.location.href = '/'; + }} + onKeyDown={e => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + window.location.href = '/'; + } + }} + > + + + Easy DataSet + + + + ); +} diff --git a/easy-dataset-main/components/Navbar/MobileDrawer.js b/easy-dataset-main/components/Navbar/MobileDrawer.js new file mode 100644 index 0000000..b8cd3b0 --- /dev/null +++ b/easy-dataset-main/components/Navbar/MobileDrawer.js @@ -0,0 +1,405 @@ +'use client'; + +import React from 'react'; +import { + Drawer, + Box, + Typography, + IconButton, + Tooltip, + List, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + Collapse +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import Link from 'next/link'; +import CloseIcon from '@mui/icons-material/Close'; +import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'; +import TokenOutlinedIcon from '@mui/icons-material/TokenOutlined'; +import QuestionAnswerOutlinedIcon from '@mui/icons-material/QuestionAnswerOutlined'; +import DatasetOutlinedIcon from '@mui/icons-material/DatasetOutlined'; +import SettingsOutlinedIcon from '@mui/icons-material/SettingsOutlined'; +import ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import ChatIcon from '@mui/icons-material/Chat'; +import ImageIcon from '@mui/icons-material/Image'; +import StorageIcon from '@mui/icons-material/Storage'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import GitHubIcon from '@mui/icons-material/GitHub'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import AssessmentOutlinedIcon from '@mui/icons-material/AssessmentOutlined'; +import PlaylistPlayIcon from '@mui/icons-material/PlaylistPlay'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import UpdateChecker from '../UpdateChecker'; +import * as styles from './styles'; + +/** + * MobileDrawer 组件 + * 移动端抽屉菜单,包含所有导航项 + */ +export default function MobileDrawer({ + theme, + drawerOpen, + toggleDrawer, + expandedMenu, + toggleMobileSubmenu, + currentProject, + onNavigateStart +}) { + const { t, i18n } = useTranslation(); + const handleNavigateStart = () => { + onNavigateStart?.(); + toggleDrawer(); + }; + + return ( + + {/* Drawer Header */} + + + + + {t('common.navigation', 'Navigation')} + + + + + + + + + + {/* Drawer Menu List */} + + {/* 数据源菜单 */} + + toggleMobileSubmenu('source')} + aria-expanded={expandedMenu === 'source'} + aria-controls="source-submenu" + role="menuitem" + sx={styles.getDrawerListItemButtonStyles(theme)} + > + + + + + {expandedMenu === 'source' ? : } + + + + + + + + + + + + + + + + + + + + {/* 数据蒸馏 */} + + + + + + + + + + {/* 问题管理 */} + + + + + + + + + + {/* 数据集管理 */} + + toggleMobileSubmenu('dataset')} + role="menuitem" + aria-expanded={expandedMenu === 'dataset'} + aria-controls="dataset-submenu" + sx={styles.getDrawerListItemButtonStyles(theme)} + > + + + + + {expandedMenu === 'dataset' ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + {/* 评估菜单 */} + + toggleMobileSubmenu('eval')} + role="menuitem" + aria-expanded={expandedMenu === 'eval'} + aria-controls="eval-submenu" + sx={styles.getDrawerListItemButtonStyles(theme)} + > + + + + + {expandedMenu === 'eval' ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + {/* 更多菜单 */} + + toggleMobileSubmenu('more')} + role="menuitem" + aria-expanded={expandedMenu === 'more'} + aria-controls="more-submenu" + sx={styles.getDrawerListItemButtonStyles(theme)} + > + + + + + {expandedMenu === 'more' ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + {/* Utilities Section */} + + + + + + + + + + + + { + window.open('https://github.com/ConardLi/easy-dataset', '_blank'); + toggleDrawer(); + }} + sx={styles.getDrawerListItemButtonStyles(theme)} + > + + + + + + + + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/Navbar/NavigationTabs.js b/easy-dataset-main/components/Navbar/NavigationTabs.js new file mode 100644 index 0000000..252b79d --- /dev/null +++ b/easy-dataset-main/components/Navbar/NavigationTabs.js @@ -0,0 +1,139 @@ +'use client'; + +import React from 'react'; +import { Box, Tabs, Tab } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import Link from 'next/link'; +import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'; +import TokenOutlinedIcon from '@mui/icons-material/TokenOutlined'; +import QuestionAnswerOutlinedIcon from '@mui/icons-material/QuestionAnswerOutlined'; +import DatasetOutlinedIcon from '@mui/icons-material/DatasetOutlined'; +import AssessmentOutlinedIcon from '@mui/icons-material/AssessmentOutlined'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; +import * as styles from './styles'; + +/** + * NavigationTabs 组件 + * 桌面端导航 Tabs,包含数据源、数据蒸馏、问题管理、数据集管理、更多等 Tab + */ +export default function NavigationTabs({ + theme, + pathname, + currentProject, + handleMenuOpen, + handleMenuClose, + onNavigateStart +}) { + const { t } = useTranslation(); + + // 计算当前 Tab 值 + const getCurrentTabValue = () => { + if (pathname.includes('/settings') || pathname.includes('/playground') || pathname.includes('/datasets-sq')) { + return 'more'; + } + if (pathname.includes('/eval-datasets') || pathname.includes('/eval-tasks')) { + return 'eval'; + } + if (pathname.includes('/datasets') || pathname.includes('/multi-turn') || pathname.includes('/image-datasets')) { + return 'datasets'; + } + if (pathname.includes('/text-split') || pathname.includes('/images')) { + return 'source'; + } + return pathname; + }; + + return ( + + + } + iconPosition="start" + label={ + + {t('common.dataSource')} + + + } + value="source" + onMouseEnter={e => handleMenuOpen(e, 'source')} + sx={styles.tabIconWrapperStyles} + /> + } + iconPosition="start" + label={t('distill.title')} + value={`/projects/${currentProject}/distill`} + component={Link} + href={`/projects/${currentProject}/distill`} + onClick={() => { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.tabIconWrapperStyles} + /> + } + iconPosition="start" + label={t('questions.title')} + value={`/projects/${currentProject}/questions`} + component={Link} + href={`/projects/${currentProject}/questions`} + onClick={() => { + onNavigateStart?.(); + handleMenuClose(); + }} + sx={styles.tabIconWrapperStyles} + /> + } + iconPosition="start" + label={ + + {t('datasets.management')} + + + } + value="datasets" + onMouseEnter={e => handleMenuOpen(e, 'dataset')} + sx={styles.tabIconWrapperStyles} + /> + } + iconPosition="start" + label={ + + {t('eval.title')} + + + } + value="eval" + onMouseEnter={e => handleMenuOpen(e, 'eval')} + sx={styles.tabIconWrapperStyles} + /> + } + iconPosition="start" + label={ + + {t('common.more')} + + + } + onMouseEnter={e => handleMenuOpen(e, 'more')} + value="more" + sx={styles.tabIconWrapperStyles} + /> + + + ); +} diff --git a/easy-dataset-main/components/Navbar/contextBarStyles.js b/easy-dataset-main/components/Navbar/contextBarStyles.js new file mode 100644 index 0000000..dda7467 --- /dev/null +++ b/easy-dataset-main/components/Navbar/contextBarStyles.js @@ -0,0 +1,247 @@ +/** + * ContextBar 组件样式 + * 包含项目选择器和模型选择器的所有样式 + */ + +import { alpha } from '@mui/material'; + +// ===== 主容器样式 ===== +export const getContextBarPaperStyles = theme => ({ + position: 'absolute', + top: 64, // Below navbar + left: 0, + zIndex: 1100, + borderBottom: 1, + borderColor: 'divider', + bgcolor: + theme.palette.mode === 'dark' + ? alpha(theme.palette.background.paper, 0.9) + : alpha(theme.palette.background.paper, 0.95), + backdropFilter: 'blur(16px)', + WebkitBackdropFilter: 'blur(16px)', + px: { xs: 2, sm: 3, md: 4 }, + py: { xs: 1.25, sm: 1.5 }, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + boxShadow: theme.palette.mode === 'dark' ? '0 1px 3px rgba(0, 0, 0, 0.2)' : '0 1px 3px rgba(0, 0, 0, 0.08)', + width: 'auto' +}); + +export const contextBarContainerStyles = { + display: 'flex', + alignItems: 'center', + gap: { xs: 1, sm: 1.5, md: 2 }, + flexWrap: 'nowrap', + width: 'auto' +}; + +// ===== 选择器容器样式 ===== +export const selectorContainerStyles = { + display: 'flex', + alignItems: 'center', + gap: 1 +}; + +// ===== 标签样式 ===== +export const labelTypographyStyles = { + color: 'text.secondary', + fontWeight: 600, + textTransform: 'uppercase', + letterSpacing: '0.5px', + fontSize: '0.7rem', + display: { xs: 'none', sm: 'block' } +}; + +// ===== Chip 内部文本样式 ===== +export const chipLabelBoxStyles = { + display: 'flex', + alignItems: 'center', + gap: 0.5 +}; + +export const chipTextStyles = { + fontWeight: 600, + fontSize: { xs: '0.8rem', sm: '0.875rem' }, + maxWidth: { xs: '80px', sm: '120px', md: '150px' }, + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis' +}; + +export const chipArrowStyles = { + ml: -0.25, + flexShrink: 0 +}; + +// ===== 项目选择器 Chip 样式 ===== +export const getProjectChipStyles = theme => ({ + minWidth: 'auto', + maxWidth: { xs: '120px', sm: '150px', md: '180px' }, + height: { xs: 32, sm: 36 }, + minWidth: { xs: 120, sm: 150, md: 180 }, + maxWidth: { xs: '120px', sm: '150px', md: '180px' }, + borderRadius: 1.5, + borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.23)' : 'rgba(0, 0, 0, 0.23)', + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.02)', + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + borderColor: 'primary.main', + bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.08)' : 'rgba(25, 118, 210, 0.04)', + transform: 'translateY(-1px)', + boxShadow: + theme.palette.mode === 'dark' ? '0 4px 12px rgba(144, 202, 249, 0.15)' : '0 4px 12px rgba(25, 118, 210, 0.15)' + }, + '&:active': { + transform: 'translateY(0)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: 2 + }, + '& .MuiChip-icon': { + color: 'text.primary', + fontSize: '1.1rem', + ml: 0.5, + flexShrink: 0 + }, + '& .MuiChip-label': { + px: 1, + overflow: 'hidden' + } +}); + +// ===== 模型选择器 Chip 样式 ===== +export const getModelChipStyles = theme => ({ + minWidth: { xs: 140, sm: 160, md: 180 }, + maxWidth: { xs: 200, sm: 280, md: 360 }, + height: { xs: 36, sm: 40 }, + borderRadius: 2, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.08)' : 'rgba(25, 118, 210, 0.04)', + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.15)' : 'rgba(25, 118, 210, 0.08)', + transform: 'translateY(-1px)', + boxShadow: + theme.palette.mode === 'dark' ? '0 4px 12px rgba(144, 202, 249, 0.25)' : '0 4px 12px rgba(25, 118, 210, 0.25)' + }, + '&:active': { + transform: 'translateY(0)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: 2 + }, + '& .MuiChip-icon': { + color: 'primary.main', + fontSize: '1.1rem', + ml: 0.5, + flexShrink: 0 + }, + '& .MuiChip-label': { + px: 1, + overflow: 'hidden' + } +}); + +// ===== 菜单样式 ===== +export const getMenuPaperStyles = theme => ({ + mt: 1, + minWidth: 240, + maxWidth: 400, + maxHeight: 400, + borderRadius: 2, + overflow: 'visible', + bgcolor: theme.palette.mode === 'dark' ? 'background.paper' : 'background.paper', + backdropFilter: 'blur(20px)', + WebkitBackdropFilter: 'blur(20px)', + boxShadow: + theme.palette.mode === 'dark' + ? '0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)' + : '0 12px 40px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)', + '&::before': { + content: '""', + display: 'block', + position: 'absolute', + top: -6, + left: 24, + width: 12, + height: 12, + bgcolor: 'background.paper', + transform: 'translateY(-50%) rotate(45deg)', + zIndex: 0, + borderLeft: `1px solid ${theme.palette.divider}`, + borderTop: `1px solid ${theme.palette.divider}` + } +}); + +export const menuListPropsStyles = { + dense: false, + sx: { py: 1 } +}; + +// ===== 菜单标题样式 ===== +export const menuHeaderTypographyStyles = { + px: 2, + py: 1, + display: 'block', + color: 'text.secondary', + fontWeight: 600, + textTransform: 'uppercase', + letterSpacing: '0.5px', + fontSize: '0.7rem' +}; + +// ===== 菜单项样式 ===== +export const getMenuItemStyles = theme => ({ + mx: 1, + borderRadius: 1.5, + minHeight: 44, + py: 1.25, + px: 1.5, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.08)' : 'rgba(25, 118, 210, 0.04)', + transform: 'translateX(4px)' + }, + '&.Mui-selected': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.16)' : 'rgba(25, 118, 210, 0.08)', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(144, 202, 249, 0.24)' : 'rgba(25, 118, 210, 0.12)' + } + } +}); + +export const menuItemIconStyles = { + minWidth: 36 +}; + +export const getMenuItemTextPrimaryProps = isSelected => ({ + variant: 'body2', + fontWeight: isSelected ? 600 : 400 +}); + +export const menuItemTextSecondaryProps = { + variant: 'caption', + sx: { fontSize: '0.7rem' } +}; + +// ===== 模型图标样式 ===== +export const modelIconStyles = { + width: 20, + height: 20, + objectFit: 'contain', + flexShrink: 0, + borderRadius: '50%', + mr: 1 +}; + +// ===== 分组标题样式 ===== +export const getProviderHeaderStyles = theme => ({ + pl: 2, + color: theme.palette.text.secondary, + fontWeight: 600, + fontSize: '0.75rem', + textTransform: 'uppercase', + letterSpacing: '0.5px', + mt: 1, + mb: 0.5 +}); diff --git a/easy-dataset-main/components/Navbar/index.js b/easy-dataset-main/components/Navbar/index.js new file mode 100644 index 0000000..b6c52c2 --- /dev/null +++ b/easy-dataset-main/components/Navbar/index.js @@ -0,0 +1,257 @@ +'use client'; + +import React, { useState, useRef, useEffect } from 'react'; +import { + AppBar, + Toolbar, + Box, + IconButton, + useTheme as useMuiTheme, + Tooltip, + useMediaQuery, + LinearProgress +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { usePathname, useRouter } from 'next/navigation'; +import { useTheme } from 'next-themes'; +import MenuIcon from '@mui/icons-material/Menu'; + +// 样式 +import * as styles from './styles'; + +// 子组件 +import Logo from './Logo'; +import ActionButtons from './ActionButtons'; +import NavigationTabs from './NavigationTabs'; +import MobileDrawer from './MobileDrawer'; +import DesktopMenus from './DesktopMenus'; +import ContextBar from './ContextBar'; + +export default function Navbar({ projects = [], currentProject }) { + const { t } = useTranslation(); + const pathname = usePathname(); + const router = useRouter(); + const theme = useMuiTheme(); + const { resolvedTheme, setTheme } = useTheme(); + const isProjectDetail = pathname.includes('/projects/') && pathname.split('/').length > 3; + + // 检测移动设备 + const isMobile = useMediaQuery(theme.breakpoints.down('lg')); + + // 移动端抽屉状态 + const [drawerOpen, setDrawerOpen] = useState(false); + const [expandedMenu, setExpandedMenu] = useState(null); + + // 桌面端菜单状态 + const [menuState, setMenuState] = useState({ anchorEl: null, menuType: null }); + const [navLoading, setNavLoading] = useState(false); + const navLoadingTimeoutRef = useRef(null); + + // ContextBar 悬浮状态 + const [contextBarHovered, setContextBarHovered] = useState(false); + const contextTriggerRef = useRef(null); + const contextBarRef = useRef(null); + + useEffect(() => { + if (!contextBarHovered) return; + + const handleOutsideClick = event => { + if (contextBarRef.current?.contains(event.target)) return; + if (contextTriggerRef.current?.contains(event.target)) return; + const projectMenuEl = document.getElementById('project-menu'); + if (projectMenuEl?.contains(event.target)) return; + setContextBarHovered(false); + }; + + document.addEventListener('pointerdown', handleOutsideClick, true); + return () => { + document.removeEventListener('pointerdown', handleOutsideClick, true); + }; + }, [contextBarHovered]); + + useEffect(() => { + if (!menuState.menuType) return; + + const handleOutsideMenuClick = event => { + if (menuState.anchorEl?.contains(event.target)) return; + if (event.target?.closest?.('.MuiMenu-root')) return; + setMenuState({ anchorEl: null, menuType: null }); + }; + + document.addEventListener('pointerdown', handleOutsideMenuClick, true); + return () => { + document.removeEventListener('pointerdown', handleOutsideMenuClick, true); + }; + }, [menuState.anchorEl, menuState.menuType]); + + useEffect(() => { + setNavLoading(false); + if (navLoadingTimeoutRef.current) { + clearTimeout(navLoadingTimeoutRef.current); + navLoadingTimeoutRef.current = null; + } + }, [pathname]); + + useEffect(() => { + if (!isProjectDetail || !currentProject) return; + const prefetchRoutes = [ + `/projects/${currentProject}/multi-turn`, + `/projects/${currentProject}/eval-datasets`, + `/projects/${currentProject}/eval-tasks` + ]; + prefetchRoutes.forEach(route => router.prefetch(route)); + }, [router, currentProject, isProjectDetail]); + + useEffect(() => { + return () => { + if (navLoadingTimeoutRef.current) { + clearTimeout(navLoadingTimeoutRef.current); + } + }; + }, []); + + const handleNavigateStart = () => { + setNavLoading(true); + if (navLoadingTimeoutRef.current) { + clearTimeout(navLoadingTimeoutRef.current); + } + navLoadingTimeoutRef.current = setTimeout(() => { + setNavLoading(false); + navLoadingTimeoutRef.current = null; + }, 12000); + }; + + const handleMenuOpen = (event, menuType) => { + setMenuState({ anchorEl: event.currentTarget, menuType }); + }; + + const handleMenuClose = () => { + setMenuState({ anchorEl: null, menuType: null }); + }; + + const isMenuOpen = menuType => menuState.menuType === menuType; + + const toggleDrawer = () => { + setDrawerOpen(!drawerOpen); + setExpandedMenu(null); + }; + + const toggleMobileSubmenu = menuType => { + setExpandedMenu(expandedMenu === menuType ? null : menuType); + }; + + const toggleTheme = () => { + setTheme(resolvedTheme === 'dark' ? 'light' : 'dark'); + }; + + return ( + <> + + + {/* 左侧: 汉堡菜单(移动端) + Logo */} + isProjectDetail && setContextBarHovered(true)} + > + {/* 汉堡菜单按钮 */} + {isProjectDetail && isMobile && ( + + + + + + )} + + {/* Logo 组件 */} + + + + {/* 中间导航 - 仅桌面端 */} + {isProjectDetail && !isMobile && ( + + )} + + {/* 右侧操作区 */} + + + {isProjectDetail && ( + + )} + + + {/* ContextBar - 在 Logo 或 ContextBar 悬浮时展示 */} + {isProjectDetail && contextBarHovered && ( + setContextBarHovered(false)}> + setContextBarHovered(false)} + /> + + )} + + {/* 移动端抽屉组件 */} + + + {/* 桌面端菜单组件 */} + + + ); +} diff --git a/easy-dataset-main/components/Navbar/styles.js b/easy-dataset-main/components/Navbar/styles.js new file mode 100644 index 0000000..af95d31 --- /dev/null +++ b/easy-dataset-main/components/Navbar/styles.js @@ -0,0 +1,374 @@ +/** + * Navbar 组件样式配置 + */ + +// AppBar 样式 +export const getAppBarStyles = theme => ({ + borderBottom: `1px solid ${theme.palette.divider}`, + bgcolor: theme.palette.mode === 'dark' ? 'background.paper' : 'primary.main', + backdropFilter: 'blur(20px)', + WebkitBackdropFilter: 'blur(20px)', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + boxShadow: theme.palette.mode === 'dark' ? '0 1px 3px rgba(0, 0, 0, 0.3)' : '0 1px 3px rgba(0, 0, 0, 0.1)' +}); + +// Toolbar 样式 +export const toolbarStyles = { + height: '64px', + minHeight: '64px !important', + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + px: { xs: 2, sm: 2, md: 3 }, + gap: 2 +}; + +// Logo 容器样式 +export const logoContainerStyles = { + display: 'flex', + alignItems: 'center', + gap: 1.5, + flexShrink: 0 +}; + +// 汉堡菜单按钮样式 +export const getHamburgerButtonStyles = theme => ({ + color: theme.palette.mode === 'dark' ? 'inherit' : 'white', + minWidth: 44, + minHeight: 44, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + transform: 'scale(1.1)', + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(255, 255, 255, 0.15)' + }, + '&:active': { + transform: 'scale(0.95)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white'}`, + outlineOffset: 2 + } +}); + +// Logo 链接样式 +export const getLogoLinkStyles = theme => ({ + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + textDecoration: 'none', + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + borderRadius: 1.5, + px: 0.5, + '&:hover': { + opacity: 0.85, + transform: 'translateY(-1px)' + }, + '&:active': { + transform: 'translateY(0)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white'}`, + outlineOffset: 2 + } +}); + +// Logo 图片样式 +export const logoImageStyles = { + width: 32, + height: 32, + mr: 1.5, + transition: 'transform 0.2s ease' +}; + +// Logo 文字样式 +export const getLogoTextStyles = theme => ({ + fontWeight: 700, + letterSpacing: '-0.5px', + fontSize: '1.125rem', + display: { xs: 'none', md: 'block' }, + color: 'white', + ...(theme.palette.mode === 'dark' && { + background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text' + }) +}); + +// 中间导航容器样式 +export const navContainerStyles = { + flexGrow: 1, + display: 'flex', + justifyContent: 'center', + mx: { lg: 1, xl: 3 }, + overflow: 'hidden' +}; + +// Tabs 样式 +export const getTabsStyles = theme => ({ + minHeight: '64px', + '& .MuiTab-root': { + minWidth: 100, + maxWidth: 180, + fontSize: '0.875rem', + fontWeight: 500, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + color: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.7)' : 'rgba(255, 255, 255, 1)', + px: 2, + minHeight: '64px', + textTransform: 'none', + letterSpacing: '0.3px', + '&:hover': { + color: 'white', + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(255, 255, 255, 0.15)' + } + }, + '& .Mui-selected': { + color: 'white !important', + fontWeight: 600, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.12)' : 'rgba(255, 255, 255, 0.2)' + }, + '& .MuiTabs-indicator': { + height: 3, + borderRadius: '3px 3px 0 0', + backgroundColor: theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white', + boxShadow: theme.palette.mode === 'dark' ? '0 0 8px rgba(103, 126, 234, 0.5)' : '0 0 8px rgba(255, 255, 255, 0.5)' + } +}); + +// Tab 图标包装器样式 +export const tabIconWrapperStyles = { + '& .MuiTab-iconWrapper': { mr: 1 } +}; + +// 右侧操作区容器样式 +export const actionAreaStyles = { + display: 'flex', + alignItems: 'center', + gap: 1, + flexShrink: 0 +}; + +// 文档/GitHub 按钮样式 +export const getIconButtonStyles = theme => ({ + display: { xs: 'none', xl: 'flex' }, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.08)' : 'rgba(255, 255, 255, 0.2)', + color: theme.palette.mode === 'dark' ? 'inherit' : 'white', + borderRadius: 1.5, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(255, 255, 255, 0.35)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.mode === 'dark' ? theme.palette.secondary.main : 'white'}`, + outlineOffset: 2 + } +}); + +// Drawer Paper 样式 +export const getDrawerPaperStyles = theme => ({ + width: { xs: '85vw', sm: 320 }, + maxWidth: 380, + bgcolor: theme.palette.mode === 'dark' ? 'background.paper' : 'background.default', + backgroundImage: + theme.palette.mode === 'dark' ? 'linear-gradient(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.05))' : 'none', + boxShadow: theme.palette.mode === 'dark' ? '0 8px 32px rgba(0, 0, 0, 0.6)' : '0 8px 32px rgba(0, 0, 0, 0.15)' +}); + +// Drawer 头部样式 +export const getDrawerHeaderStyles = theme => ({ + p: 2.5, + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + borderBottom: `1px solid ${theme.palette.divider}`, + minHeight: 64 +}); + +// Drawer 关闭按钮样式 +export const getDrawerCloseButtonStyles = theme => ({ + minWidth: 44, + minHeight: 44, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + transform: 'rotate(90deg)', + bgcolor: 'action.hover' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: 2 + } +}); + +// Drawer 列表样式 +export const drawerListStyles = { + pt: 1, + px: 1 +}; + +// Drawer 列表项按钮样式 +export const getDrawerListItemButtonStyles = theme => ({ + borderRadius: '8px', + minHeight: 48, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.12)' : 'rgba(103, 126, 234, 0.08)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: -2 + } +}); + +// Drawer 子菜单容器样式 +export const getDrawerSubmenuContainerStyles = theme => ({ + bgcolor: theme.palette.mode === 'dark' ? 'rgba(0, 0, 0, 0.2)' : 'rgba(0, 0, 0, 0.02)', + borderRadius: '8px', + my: 0.5 +}); + +// Drawer 子菜单项样式 +export const getDrawerSubmenuItemStyles = theme => ({ + pl: 4, + mx: 1, + borderRadius: '8px', + minHeight: 44, + py: 1.5, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.08)' : 'rgba(103, 126, 234, 0.05)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: -2 + } +}); + +// Drawer 工具区域样式 +export const getDrawerUtilitiesStyles = theme => ({ + mt: 'auto', + pt: 2, + borderTop: `1px solid ${theme.palette.divider}` +}); + +// Menu Paper 样式 +export const getMenuPaperStyles = theme => ({ + mt: 1.5, + borderRadius: '12px', + minWidth: 220, + overflow: 'visible', + bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)', + backdropFilter: 'blur(20px)', + WebkitBackdropFilter: 'blur(20px)', + boxShadow: + theme.palette.mode === 'dark' + ? '0 12px 40px rgba(0, 0, 0, 0.6), 0 0 0 1px rgba(255, 255, 255, 0.1)' + : '0 12px 40px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.05)', + '&::before': { + content: '""', + display: 'block', + position: 'absolute', + top: 0, + right: '50%', + width: 12, + height: 12, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)', + transform: 'translateY(-50%) translateX(50%) rotate(45deg)', + zIndex: 0, + boxShadow: theme.palette.mode === 'dark' ? '-2px -2px 4px rgba(0, 0, 0, 0.3)' : '-2px -2px 4px rgba(0, 0, 0, 0.1)' + } +}); + +// Menu 列表样式 +export const menuListStyles = { + py: 1.5 +}; + +// Menu 项样式 +export const getMenuItemStyles = theme => ({ + mx: 1, + borderRadius: '8px', + py: 1.25, + minHeight: 44, + transition: 'all 0.2s cubic-bezier(0.4, 0, 0.2, 1)', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.15)' : 'rgba(103, 126, 234, 0.1)', + transform: 'translateX(4px)' + }, + '&:focus-visible': { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: -2 + } +}); + +// Dataset/More Menu Paper 样式(简化版) +export const getSimpleMenuPaperStyles = theme => ({ + mt: 1.5, + borderRadius: '12px', + minWidth: 220, + overflow: 'visible', + bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)', + backdropFilter: 'blur(20px)', + boxShadow: + theme.palette.mode === 'dark' + ? '0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.1)' + : '0 8px 32px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(0, 0, 0, 0.05)', + '&::before': { + content: '""', + display: 'block', + position: 'absolute', + top: 0, + right: '50%', + width: 12, + height: 12, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.98)' : 'rgba(255, 255, 255, 0.98)', + transform: 'translateY(-50%) translateX(50%) rotate(45deg)', + zIndex: 0 + } +}); + +// 简化 Menu 列表样式 +export const simpleMenuListStyles = { + py: 1 +}; + +// 简化 Menu 项样式 +export const getSimpleMenuItemStyles = theme => ({ + mx: 0.75, + borderRadius: '8px', + py: 1, + transition: 'all 0.15s ease', + '&:hover': { + bgcolor: theme.palette.mode === 'dark' ? 'rgba(103, 126, 234, 0.15)' : 'rgba(103, 126, 234, 0.1)', + transform: 'translateX(4px)' + } +}); + +// ListItemIcon 样式 +export const listItemIconStyles = { + minWidth: 40 +}; + +export const smallListItemIconStyles = { + minWidth: 36 +}; + +// ListItemText 样式 +export const listItemTextStyles = { + fontWeight: 600, + fontSize: '0.95rem' +}; + +export const smallListItemTextStyles = { + fontSize: '0.9rem', + fontWeight: 500 +}; + +// 图标颜色样式 +export const getIconColorStyles = theme => ({ + color: theme.palette.mode === 'dark' ? 'primary.light' : 'primary.main' +}); + +export const getPrimaryIconColorStyles = theme => ({ + color: theme.palette.primary.main +}); diff --git a/easy-dataset-main/components/TaskIcon.js b/easy-dataset-main/components/TaskIcon.js new file mode 100644 index 0000000..1bd9fe6 --- /dev/null +++ b/easy-dataset-main/components/TaskIcon.js @@ -0,0 +1,223 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { Badge, IconButton, Tooltip, CircularProgress, Menu, MenuItem, Divider, ListItemIcon } from '@mui/material'; +import TaskAltIcon from '@mui/icons-material/TaskAlt'; +import ListAltIcon from '@mui/icons-material/ListAlt'; +import QuizIcon from '@mui/icons-material/Quiz'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import CleaningServicesIcon from '@mui/icons-material/CleaningServices'; +import { useTranslation } from 'react-i18next'; +import { useRouter, usePathname } from 'next/navigation'; +import useFileProcessingStatus from '@/hooks/useFileProcessingStatus'; +import { useAtomValue } from 'jotai/index'; +import { selectedModelInfoAtom } from '@/lib/store'; +import axios from 'axios'; +import { toast } from 'sonner'; + +export default function TaskIcon({ projectId, theme }) { + const { t, i18n } = useTranslation(); + const router = useRouter(); + const pathname = usePathname(); + const [tasks, setTasks] = useState([]); + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const isMenuOpen = Boolean(menuAnchorEl); + const selectedModel = useAtomValue(selectedModelInfoAtom); + const { setTaskFileProcessing, setTask } = useFileProcessingStatus(); + + const fetchPendingTasks = async () => { + if (!projectId) return; + + try { + const response = await axios.get(`/api/projects/${projectId}/tasks/list?status=0`); + if (response.data?.code === 0) { + const pendingTasks = response.data.data || []; + setTasks(pendingTasks); + + const hasActiveFileTask = pendingTasks.some( + task => task.projectId === projectId && task.taskType === 'file-processing' + ); + setTaskFileProcessing(hasActiveFileTask); + + if (hasActiveFileTask) { + const activeTask = pendingTasks.find( + task => task.projectId === projectId && task.taskType === 'file-processing' + ); + try { + const detailInfo = JSON.parse(activeTask?.detail || '{}'); + setTask(detailInfo); + } catch { + setTask(null); + } + } + } + } catch (error) { + console.error('Failed to fetch task list:', error); + } + }; + + useEffect(() => { + if (!projectId) return; + + fetchPendingTasks(); + + const intervalId = setInterval(() => { + fetchPendingTasks(); + }, 10000); + + return () => { + clearInterval(intervalId); + }; + }, [projectId]); + + useEffect(() => { + setMenuAnchorEl(null); + }, [pathname]); + + const handleOpenTaskList = () => { + setMenuAnchorEl(null); + router.push(`/projects/${projectId}/tasks`); + }; + + const handleMenuOpen = event => { + if (isMenuOpen) { + setMenuAnchorEl(null); + return; + } + setMenuAnchorEl(event.currentTarget); + }; + + const handleMenuClose = () => { + setMenuAnchorEl(null); + }; + + const createBatchTask = async (taskType, detail) => { + if (!projectId || !selectedModel?.id) { + toast.error(t('textSplit.selectModelFirst')); + return; + } + + try { + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType, + modelInfo: selectedModel, + language: i18n.language, + detail + }); + + if (response.data?.code === 0) { + toast.success(t('tasks.createSuccess')); + await fetchPendingTasks(); + } else { + toast.error(`${t('tasks.createFailed')}: ${response.data?.message || ''}`); + } + } catch (error) { + console.error('Create batch task failed:', error); + toast.error(`${t('tasks.createFailed')}: ${error.message}`); + } + }; + + const handleCreateAutoQuestionTask = async () => { + await createBatchTask('question-generation', '批量生成问题任务'); + handleMenuClose(); + }; + + const handleCreateAutoEvalTask = async () => { + await createBatchTask('eval-generation', '批量生成评估集任务'); + handleMenuClose(); + }; + + const handleCreateAutoCleaningTask = async () => { + await createBatchTask('data-cleaning', '批量数据清洗任务'); + handleMenuClose(); + }; + + const renderTaskIcon = () => { + const pendingTasks = tasks.filter(task => task.status === 0); + + if (pendingTasks.length > 0) { + return ( + + + + ); + } + + return ; + }; + + const getTooltipText = () => { + const pendingTasks = tasks.filter(task => task.status === 0); + + if (pendingTasks.length > 0) { + return t('tasks.pending', { count: pendingTasks.length }); + } + + return t('tasks.completed'); + }; + + if (!projectId) return null; + + return ( + <> + + + {renderTaskIcon()} + + + + + + + + + {t('tasks.title')} + + + + + + + + + {t('textSplit.autoGenerateQuestions', { defaultValue: '自动提取问题' })} + + + + + + + {t('textSplit.autoEvalGeneration', { defaultValue: '自动生成评估集' })} + + + + + + + {t('textSplit.autoDataCleaning', { defaultValue: '自动数据清洗' })} + + + + ); +} diff --git a/easy-dataset-main/components/ThemeRegistry.js b/easy-dataset-main/components/ThemeRegistry.js new file mode 100644 index 0000000..9911448 --- /dev/null +++ b/easy-dataset-main/components/ThemeRegistry.js @@ -0,0 +1,342 @@ +'use client'; + +import { createTheme, ThemeProvider } from '@mui/material/styles'; +import CssBaseline from '@mui/material/CssBaseline'; +import { ThemeProvider as NextThemeProvider, useTheme } from 'next-themes'; +import { useEffect, useState } from 'react'; + +// 导入字体 +import '@fontsource/inter/300.css'; +import '@fontsource/inter/400.css'; +import '@fontsource/inter/500.css'; +import '@fontsource/inter/600.css'; +import '@fontsource/inter/700.css'; +import '@fontsource/jetbrains-mono/400.css'; +import '@fontsource/jetbrains-mono/500.css'; + +// 创建主题配置 +const getTheme = mode => { + // 主色调 + const mainBlue = '#2A5CAA'; + const darkGray = '#2D2D2D'; + + // 辅助色 - 数据可视化色谱 + const dataVizColors = [ + '#6366F1', // 紫蓝色 + '#10B981', // 绿色 + '#F59E0B', // 琥珀色 + '#EC4899', // 粉色 + '#8B5CF6', // 紫色 + '#3B82F6' // 蓝色 + ]; + + // 状态色 + const successColor = '#10B981'; // 翡翠绿 + const warningColor = '#F59E0B'; // 琥珀色 + const errorColor = '#EF4444'; // 珊瑚红 + + // 渐变色 + const gradientPrimary = 'linear-gradient(90deg, #2A5CAA 0%, #8B5CF6 100%)'; + + // 根据模式调整颜色 + return createTheme({ + palette: { + mode, + primary: { + main: mainBlue, + dark: '#1E4785', + light: '#4878C6', + contrastText: '#FFFFFF' + }, + secondary: { + main: '#8B5CF6', + dark: '#7039F2', + light: '#A78BFA', + contrastText: '#FFFFFF' + }, + error: { + main: errorColor, + dark: '#DC2626', + light: '#F87171' + }, + warning: { + main: warningColor, + dark: '#D97706', + light: '#FBBF24' + }, + success: { + main: successColor, + dark: '#059669', + light: '#34D399' + }, + background: { + default: mode === 'dark' ? '#121212' : '#F8F9FA', + paper: mode === 'dark' ? '#1E1E1E' : '#FFFFFF', + subtle: mode === 'dark' ? '#2A2A2A' : '#F3F4F6' + }, + text: { + primary: mode === 'dark' ? '#F3F4F6' : darkGray, + secondary: mode === 'dark' ? '#9CA3AF' : '#6B7280', + disabled: mode === 'dark' ? '#4B5563' : '#9CA3AF' + }, + divider: mode === 'dark' ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.12)', + dataViz: dataVizColors, + gradient: { + primary: gradientPrimary + } + }, + typography: { + fontFamily: + '"Inter", "HarmonyOS Sans", "PingFang SC", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', + fontSize: 14, + fontWeightLight: 300, + fontWeightRegular: 400, + fontWeightMedium: 500, + fontWeightBold: 600, + h1: { + fontSize: '2rem', // 32px + fontWeight: 600, + lineHeight: 1.2, + letterSpacing: '-0.01em' + }, + h2: { + fontSize: '1.5rem', // 24px + fontWeight: 600, + lineHeight: 1.3, + letterSpacing: '-0.005em' + }, + h3: { + fontSize: '1.25rem', // 20px + fontWeight: 600, + lineHeight: 1.4 + }, + h4: { + fontSize: '1.125rem', // 18px + fontWeight: 600, + lineHeight: 1.4 + }, + h5: { + fontSize: '1rem', // 16px + fontWeight: 600, + lineHeight: 1.5 + }, + h6: { + fontSize: '0.875rem', // 14px + fontWeight: 600, + lineHeight: 1.5 + }, + body1: { + fontSize: '1rem', // 16px + lineHeight: 1.5 + }, + body2: { + fontSize: '0.875rem', // 14px + lineHeight: 1.5 + }, + caption: { + fontSize: '0.75rem', // 12px + lineHeight: 1.5 + }, + code: { + fontFamily: '"JetBrains Mono", monospace', + fontSize: '0.875rem' + } + }, + shape: { + borderRadius: 8 + }, + spacing: 8, // 基础间距单位为8px + components: { + MuiCssBaseline: { + styleOverrides: { + body: { + scrollbarWidth: 'thin', + scrollbarColor: mode === 'dark' ? '#4B5563 transparent' : '#9CA3AF transparent', + '&::-webkit-scrollbar': { + width: '8px', + height: '8px' + }, + '&::-webkit-scrollbar-track': { + background: 'transparent' + }, + '&::-webkit-scrollbar-thumb': { + background: mode === 'dark' ? '#4B5563' : '#9CA3AF', + borderRadius: '4px' + } + }, + // 确保代码块使用 JetBrains Mono 字体 + 'code, pre': { + fontFamily: '"JetBrains Mono", monospace' + }, + // 自定义渐变文本的通用样式 + '.gradient-text': { + background: gradientPrimary, + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text', + textFillColor: 'transparent' + } + } + }, + MuiButton: { + styleOverrides: { + root: { + textTransform: 'none', + fontWeight: 500, + borderRadius: '8px', + padding: '6px 16px' + }, + contained: { + boxShadow: 'none', + '&:hover': { + boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.1)' + } + }, + containedPrimary: { + background: mainBlue, + '&:hover': { + backgroundColor: '#1E4785' + } + }, + containedSecondary: { + background: '#8B5CF6', + '&:hover': { + backgroundColor: '#7039F2' + } + }, + outlined: { + borderWidth: '1.5px', + '&:hover': { + borderWidth: '1.5px' + } + } + } + }, + MuiAppBar: { + styleOverrides: { + root: { + boxShadow: 'none', + background: mode === 'dark' ? '#1A1A1A' : mainBlue + } + } + }, + MuiCard: { + styleOverrides: { + root: { + borderRadius: '12px', + boxShadow: mode === 'dark' ? '0px 4px 8px rgba(0, 0, 0, 0.4)' : '0px 4px 8px rgba(0, 0, 0, 0.05)' + } + } + }, + MuiPaper: { + styleOverrides: { + root: { + borderRadius: '12px' + } + } + }, + MuiChip: { + styleOverrides: { + root: { + borderRadius: '6px', + fontWeight: 500 + } + } + }, + MuiTableHead: { + styleOverrides: { + root: { + '& .MuiTableCell-head': { + fontWeight: 600, + backgroundColor: mode === 'dark' ? '#2A2A2A' : '#F3F4F6' + } + } + } + }, + MuiTabs: { + styleOverrides: { + indicator: { + height: '3px', + borderRadius: '3px 3px 0 0' + } + } + }, + MuiTab: { + styleOverrides: { + root: { + textTransform: 'none', + fontWeight: 500, + '&.Mui-selected': { + fontWeight: 600 + } + } + } + }, + MuiListItemButton: { + styleOverrides: { + root: { + borderRadius: '8px' + } + } + }, + MuiModal: { + defaultProps: { + disableScrollLock: true + } + }, + MuiDialog: { + defaultProps: { + disableScrollLock: true + } + }, + MuiPopover: { + defaultProps: { + disableScrollLock: true + } + }, + MuiMenu: { + defaultProps: { + disableScrollLock: true + } + }, + MuiDialogTitle: { + styleOverrides: { + root: { + fontSize: '1.25rem', + fontWeight: 600 + } + } + } + } + }); +}; + +export default function ThemeRegistry({ children }) { + const [mounted, setMounted] = useState(false); + + useEffect(() => { + setMounted(true); + }, []); + + if (!mounted) { + return null; + } + + return ( + + {children} + + ); +} + +function InnerThemeRegistry({ children }) { + const { resolvedTheme } = useTheme(); + const theme = getTheme(resolvedTheme === 'dark' ? 'dark' : 'light'); + + return ( + + + {children} + + ); +} diff --git a/easy-dataset-main/components/UpdateChecker.js b/easy-dataset-main/components/UpdateChecker.js new file mode 100644 index 0000000..3b0d353 --- /dev/null +++ b/easy-dataset-main/components/UpdateChecker.js @@ -0,0 +1,235 @@ +import React, { useState, useEffect } from 'react'; +import { Box, Button, Snackbar, Alert, Typography, Link, CircularProgress, LinearProgress } from '@mui/material'; +import UpdateIcon from '@mui/icons-material/Update'; +import { useTranslation } from 'react-i18next'; + +const UpdateChecker = () => { + const { t } = useTranslation(); + const [updateAvailable, setUpdateAvailable] = useState(false); + const [updateInfo, setUpdateInfo] = useState(null); + const [open, setOpen] = useState(false); + const [checking, setChecking] = useState(false); + const [downloading, setDownloading] = useState(false); + const [downloadProgress, setDownloadProgress] = useState(0); + const [updateDownloaded, setUpdateDownloaded] = useState(false); + const [updateError, setUpdateError] = useState(null); + + // 检查更新 + const checkForUpdates = async () => { + if (!window.electron?.updater) { + console.warn('Update feature is not available, possibly running in browser environment'); + return; + } + + try { + setChecking(true); + setUpdateError(null); + + const result = await window.electron.updater.checkForUpdates(); + console.log('Update check result:', result); + + // 返回当前版本信息 + if (result) { + setUpdateInfo(prev => ({ + ...prev, + currentVersion: result.currentVersion + })); + } + } catch (error) { + console.error('Failed to check for updates:', error); + // setUpdateError(error.message || 'Failed to check for updates'); + } finally { + setChecking(false); + } + }; + + // 下载更新 + const downloadUpdate = async () => { + if (!window.electron?.updater) return; + + try { + setDownloading(true); + setUpdateError(null); + await window.electron.updater.downloadUpdate(); + } catch (error) { + console.error('下载更新失败:', error); + setUpdateError(error.message || '下载更新失败'); + setDownloading(false); + } + }; + + // 安装更新 + const installUpdate = async () => { + if (!window.electron?.updater) return; + + try { + await window.electron.updater.installUpdate(); + } catch (error) { + console.error('Failed to install update:', error); + // setUpdateError(error.message || 'Failed to install update'); + } + }; + + // 设置更新事件监听 + useEffect(() => { + if (!window.electron?.updater) return; + + // 有可用更新 + const removeUpdateAvailable = window.electron.updater.onUpdateAvailable(info => { + console.log('发现新版本:', info); + setUpdateAvailable(true); + setUpdateInfo(prev => ({ + ...prev, + ...info, + releaseUrl: `https://github.com/ConardLi/easy-dataset/releases` + })); + setOpen(true); + }); + + // 没有可用更新 + const removeUpdateNotAvailable = window.electron.updater.onUpdateNotAvailable(() => { + console.log('没有可用更新'); + setUpdateAvailable(false); + }); + + // 更新错误 + const removeUpdateError = window.electron.updater.onUpdateError(error => { + console.error('更新错误:', error); + // setUpdateError(error); + }); + + // 下载进度 + const removeDownloadProgress = window.electron.updater.onDownloadProgress(progress => { + console.log('下载进度:', progress); + setDownloadProgress(progress.percent || 0); + }); + + // 更新下载完成 + const removeUpdateDownloaded = window.electron.updater.onUpdateDownloaded(info => { + console.log('更新下载完成:', info); + setDownloading(false); + setUpdateDownloaded(true); + }); + + // 组件挂载时检查更新 + const timer = setTimeout(() => { + checkForUpdates(); + }, 5000); + + // 清理函数 + return () => { + clearTimeout(timer); + removeUpdateAvailable(); + removeUpdateNotAvailable(); + removeUpdateError(); + removeDownloadProgress(); + removeUpdateDownloaded(); + }; + }, []); + + // 定期检查更新(每小时一次) + useEffect(() => { + if (!window.electron?.updater) return; + + const interval = setInterval( + () => { + checkForUpdates(); + }, + 60 * 60 * 1000 + ); + + return () => clearInterval(interval); + }, []); + + const handleClose = () => { + setOpen(false); + }; + + // 如果没有更新或者不在 Electron 环境中,不显示任何内容 + if (!updateAvailable && !open) return null; + + return ( + <> + {updateAvailable && ( + + )} + + + + + {t('update.newVersionAvailable')} + + {updateInfo && ( + <> + + {t('update.currentVersion')}: {updateInfo.currentVersion} + + + {t('update.latestVersion')}: {updateInfo.version} + + + )} + + {checking && ( + + + {t('update.checking')} + + )} + + {updateError && ( + + {updateError} + + )} + + {downloading && ( + + + {t('update.downloading')}: {Math.round(downloadProgress)}% + + + + )} + + + {/* {!downloading && !updateDownloaded ? ( + + ) : updateDownloaded ? ( + + ) : null} */} + + {updateInfo?.releaseUrl && ( + + + + )} + + + + + + ); +}; + +export default UpdateChecker; diff --git a/easy-dataset-main/components/common/MessageAlert.js b/easy-dataset-main/components/common/MessageAlert.js new file mode 100644 index 0000000..c2c0acd --- /dev/null +++ b/easy-dataset-main/components/common/MessageAlert.js @@ -0,0 +1,23 @@ +'use client'; + +import { Snackbar, Alert } from '@mui/material'; + +export default function MessageAlert({ message, onClose }) { + if (!message) return null; + + const severity = message.severity || 'error'; + const text = typeof message === 'string' ? message : message.message; + + return ( + + + {text} + + + ); +} diff --git a/easy-dataset-main/components/conversations/ConversationContent.js b/easy-dataset-main/components/conversations/ConversationContent.js new file mode 100644 index 0000000..600f6ac --- /dev/null +++ b/easy-dataset-main/components/conversations/ConversationContent.js @@ -0,0 +1,88 @@ +'use client'; + +import { Box, Typography, Card, CardContent, Chip, TextField } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +/** + * 多轮对话内容展示和编辑组件 + */ +export default function ConversationContent({ messages, editMode, onMessageChange, conversation }) { + const { t } = useTranslation(); + + // 获取角色显示信息 + const getRoleDisplay = role => { + switch (role) { + case 'system': + return { name: t('datasets.system'), color: 'default' }; + case 'user': + return { name: conversation?.roleA || t('datasets.user'), color: 'primary' }; + case 'assistant': + return { name: conversation?.roleB || t('datasets.assistant'), color: 'secondary' }; + default: + return { name: role, color: 'default' }; + } + }; + + return ( + + + {t('datasets.conversationContent')} + + + + {messages.map((message, index) => { + const roleInfo = getRoleDisplay(message.role); + return ( + + + + {message.role !== 'system' && ( + + {t('datasets.round', { round: Math.floor((index + 1) / 2) + 1 })} + + )} + + + + {editMode ? ( + onMessageChange && onMessageChange(index, e.target.value)} + variant="outlined" + size="small" + sx={{ + '& .MuiInputBase-input': { + fontFamily: 'inherit', + fontSize: '0.875rem', + lineHeight: 1.5 + } + }} + /> + ) : ( + + {message.content} + + )} + + + + ); + })} + + + ); +} diff --git a/easy-dataset-main/components/conversations/ConversationHeader.js b/easy-dataset-main/components/conversations/ConversationHeader.js new file mode 100644 index 0000000..78f48aa --- /dev/null +++ b/easy-dataset-main/components/conversations/ConversationHeader.js @@ -0,0 +1,87 @@ +'use client'; + +import { Box, Button, Divider, Typography, IconButton, CircularProgress, Paper, Tooltip } from '@mui/material'; +import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; +import SaveIcon from '@mui/icons-material/Save'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/navigation'; + +/** + * 多轮对话详情页面的头部导航组件 + */ +export default function ConversationHeader({ + projectId, + conversationId, + conversation, + editMode, + saving, + onEdit, + onSave, + onCancel, + onDelete, + onNavigate +}) { + const router = useRouter(); + const { t } = useTranslation(); + + return ( + + + + + + {t('datasets.conversationDetail')} + {conversation && ( + + {conversation.scenario && ( + <> + {conversation.scenario} • {conversation.turnCount}/{conversation.maxTurns} 轮 + + )} + + )} + + + + {/* 翻页按钮 */} + onNavigate && onNavigate('prev')}> + + + onNavigate && onNavigate('next')}> + + + + + {/* 编辑/保存按钮 */} + {editMode ? ( + <> + + + + ) : ( + <> + + + + )} + + + + ); +} diff --git a/easy-dataset-main/components/conversations/ConversationMetadata.js b/easy-dataset-main/components/conversations/ConversationMetadata.js new file mode 100644 index 0000000..7b600ef --- /dev/null +++ b/easy-dataset-main/components/conversations/ConversationMetadata.js @@ -0,0 +1,77 @@ +'use client'; + +import { Box, Typography, Chip, Tooltip, alpha, Paper } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useTheme } from '@mui/material/styles'; + +/** + * 多轮对话元数据展示组件 + */ +export default function ConversationMetadata({ conversation }) { + const { t } = useTranslation(); + const theme = useTheme(); + + if (!conversation) return null; + + return ( + + + {t('datasets.metadata')} + + + + + {conversation.scenario && ( + + )} + + + + {conversation.roleA && ( + + )} + + {conversation.roleB && ( + + )} + + + + {conversation.confirmed && ( + + )} + + + ); +} diff --git a/easy-dataset-main/components/conversations/ConversationRatingSection.js b/easy-dataset-main/components/conversations/ConversationRatingSection.js new file mode 100644 index 0000000..3a7522e --- /dev/null +++ b/easy-dataset-main/components/conversations/ConversationRatingSection.js @@ -0,0 +1,201 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Box, Typography, Divider, Paper, TextField } from '@mui/material'; +import { toast } from 'sonner'; +import StarRating from '@/components/datasets/StarRating'; +import TagSelector from '@/components/datasets/TagSelector'; +import NoteInput from '@/components/datasets/NoteInput'; +import { useTranslation } from 'react-i18next'; + +/** + * 多轮对话评分、标签、备注综合组件 + */ +export default function ConversationRatingSection({ conversation, projectId, onUpdate }) { + const { t } = useTranslation(); + const [availableTags, setAvailableTags] = useState([]); + const [loading, setLoading] = useState(false); + + // 解析对话中的标签 + const parseConversationTags = tagsString => { + try { + if (typeof tagsString === 'string' && tagsString.trim()) { + return tagsString.split(/\s+/).filter(tag => tag.length > 0); + } + return []; + } catch (e) { + return []; + } + }; + + // 本地状态管理 + const [localScore, setLocalScore] = useState(conversation.score || 0); + const [localTags, setLocalTags] = useState(() => parseConversationTags(conversation.tags)); + const [localNote, setLocalNote] = useState(conversation.note || ''); + + // 获取项目中已使用的标签 + useEffect(() => { + const fetchAvailableTags = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/tags`); + if (response.ok) { + const data = await response.json(); + setAvailableTags(data.tags || []); + } + } catch (error) { + console.error('获取可用标签失败:', error); + } + }; + + if (projectId) { + fetchAvailableTags(); + } + }, [projectId]); + + // 同步props中的conversation到本地状态 + useEffect(() => { + setLocalScore(conversation.score || 0); + setLocalTags(parseConversationTags(conversation.tags)); + setLocalNote(conversation.note || ''); + }, [conversation]); + + // 更新对话元数据 + const updateMetadata = async updates => { + if (loading) return; + + // 立即更新本地状态 + if (updates.score !== undefined) { + setLocalScore(updates.score); + } + if (updates.tagsArray !== undefined) { + setLocalTags(updates.tagsArray); + } + if (updates.note !== undefined) { + setLocalNote(updates.note); + } + + setLoading(true); + try { + const response = await fetch(`/api/projects/${projectId}/dataset-conversations/${conversation.id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + score: updates.score, + tags: updates.tags, + note: updates.note + }) + }); + + if (!response.ok) { + throw new Error(t('datasets.saveFailed')); + } + + const result = await response.json(); + toast.success(t('datasets.saveSuccess')); + + // 如果有父组件的更新回调,调用它 + if (onUpdate) { + onUpdate(result.data); + } + } catch (error) { + console.error('更新对话元数据失败:', error); + toast.error(error.message || t('datasets.saveFailed')); + + // 出错时恢复本地状态 + if (updates.score !== undefined) { + setLocalScore(conversation.score || 0); + } + if (updates.tagsArray !== undefined) { + setLocalTags(parseConversationTags(conversation.tags)); + } + if (updates.note !== undefined) { + setLocalNote(conversation.note || ''); + } + } finally { + setLoading(false); + } + }; + + // 处理评分变更 + const handleScoreChange = newScore => { + updateMetadata({ score: newScore }); + }; + + // 处理标签变更 + const handleTagsChange = newTags => { + const tagsString = Array.isArray(newTags) ? newTags.join(' ') : ''; + updateMetadata({ tags: tagsString, tagsArray: newTags }); + }; + + // 处理备注变更 + const handleNoteChange = newNote => { + updateMetadata({ note: newNote }); + }; + + return ( + + {/* 评分区域 */} + + + {t('datasets.rating')} + + + + + + + {/* 标签区域 */} + + + {t('datasets.customTags')} + + + + + + + {/* 备注区域 */} + + + + + {/* 确认状态 */} + {/* + + {t('datasets.confirmationStatus')} + + + {conversation.confirmed ? t('datasets.confirmed') : t('datasets.unconfirmed')} + + */} + + {/* AI评估 */} + {conversation.aiEvaluation && ( + <> + + + + {t('datasets.aiEvaluation')} + + + {conversation.aiEvaluation} + + + + )} + + ); +} diff --git a/easy-dataset-main/components/dataset-square/DatasetSearchBar.js b/easy-dataset-main/components/dataset-square/DatasetSearchBar.js new file mode 100644 index 0000000..d84da83 --- /dev/null +++ b/easy-dataset-main/components/dataset-square/DatasetSearchBar.js @@ -0,0 +1,264 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { + Box, + TextField, + InputAdornment, + List, + ListItem, + ListItemButton, + ListItemText, + Paper, + Typography, + ClickAwayListener, + Fade, + Avatar, + useTheme, + alpha +} from '@mui/material'; +import SearchIcon from '@mui/icons-material/Search'; +import LaunchIcon from '@mui/icons-material/Launch'; +import TravelExploreIcon from '@mui/icons-material/TravelExplore'; +import sites from '@/constant/sites.json'; +import { useTranslation } from 'react-i18next'; + +export function DatasetSearchBar() { + const [searchQuery, setSearchQuery] = useState(''); + const [showSuggestions, setShowSuggestions] = useState(false); + const [recentSearches, setRecentSearches] = useState([]); + const searchRef = useRef(null); + const suggestionsRef = useRef(null); + const theme = useTheme(); + const { t } = useTranslation(); + + // 从 localStorage 加载最近搜索 + useEffect(() => { + const savedSearches = localStorage.getItem('recentDatasetSearches'); + if (savedSearches) { + try { + const searches = JSON.parse(savedSearches); + setRecentSearches(searches); + } catch (e) { + console.error('解析最近搜索失败', e); + } + } + }, []); + + // 处理搜索输入变化 + const handleSearchChange = event => { + setSearchQuery(event.target.value); + if (event.target.value) { + setShowSuggestions(true); + } else { + setShowSuggestions(false); + } + }; + + // 处理回车搜索 + const handleSearchSubmit = event => { + if (event.key === 'Enter' && searchQuery.trim()) { + // 默认使用第一个搜索引擎 + if (sites.length > 0) { + handleSuggestionClick(sites[0]); + } + } + }; + + // 保存最近搜索 + const saveRecentSearch = query => { + if (!query.trim()) return; + + // 添加到最近搜索并去重 + const updatedSearches = [query, ...recentSearches.filter(s => s !== query)].slice(0, 5); + setRecentSearches(updatedSearches); + + // 保存到 localStorage + try { + localStorage.setItem('recentDatasetSearches', JSON.stringify(updatedSearches)); + } catch (e) { + console.error('保存最近搜索失败', e); + } + }; + + // 处理点击搜索建议 + const handleSuggestionClick = site => { + if (searchQuery.trim()) { + // 根据不同网站处理搜索参数 + let searchUrl = site.link; + + // 如果链接中不包含问号,则添加搜索参数 + if (site.link.includes('huggingface.co')) { + searchUrl = `${site.link}?sort=trending&search=${encodeURIComponent(searchQuery)}`; + } else if (site.link.includes('kaggle.com')) { + searchUrl = `${site.link}?search=${encodeURIComponent(searchQuery)}`; + } else if (site.link.includes('datasetsearch.research.google.com')) { + searchUrl = `${site.link}/search?query=${encodeURIComponent(searchQuery)}&src=0`; + } else if (site.link.includes('paperswithcode.com')) { + searchUrl = `${site.link}?q=${encodeURIComponent(searchQuery)}`; + } else if (site.link.includes('modelscope.cn')) { + searchUrl = `${site.link}?query=${encodeURIComponent(searchQuery)}`; + } else if (site.link.includes('opendatalab.com')) { + searchUrl = `${site.link}?keywords=${encodeURIComponent(searchQuery)}`; + } else if (site.link.includes('tianchi.aliyun.com')) { + searchUrl = `${site.link}?q=${encodeURIComponent(searchQuery)}`; + } else { + // 默认处理方式,在URL后添加搜索参数 + searchUrl = `${site.link}${site.link.includes('?') ? '&' : '?'}search=${encodeURIComponent(searchQuery)}`; + } + + // 保存最近搜索 + saveRecentSearch(searchQuery); + + window.open(searchUrl, '_blank'); + } + setShowSuggestions(false); + }; + + // 处理点击外部关闭建议 + const handleClickAway = event => { + // 确保点击的不是建议框本身 + if (suggestionsRef.current && !suggestionsRef.current.contains(event.target)) { + setShowSuggestions(false); + } + }; + + return ( + + + searchQuery && setShowSuggestions(true)} + InputProps={{ + startAdornment: ( + + + + ), + sx: { + height: 56, + borderRadius: 3, + backgroundColor: + theme.palette.mode === 'dark' + ? alpha(theme.palette.background.default, 0.6) + : alpha(theme.palette.background.default, 0.8), + backdropFilter: 'blur(8px)', + px: 2, + transition: 'all 0.3s ease', + boxShadow: `0 0 0 1px ${alpha(theme.palette.primary.main, 0.15)}`, + '&.MuiOutlinedInput-root': { + '& fieldset': { + borderColor: 'transparent' + }, + '&:hover fieldset': { + borderColor: 'transparent' + }, + '&.Mui-focused': { + boxShadow: `0 0 0 2px ${alpha(theme.palette.primary.main, 0.3)}`, + backgroundColor: + theme.palette.mode === 'dark' + ? alpha(theme.palette.background.paper, 0.8) + : alpha(theme.palette.common.white, 0.95) + }, + '&.Mui-focused fieldset': { + borderColor: 'transparent' + } + } + } + }} + sx={{ + mb: 1, + '& .MuiInputBase-input': { + fontSize: '1rem', + fontWeight: 500, + color: theme.palette.text.primary + }, + '& .MuiInputBase-input::placeholder': { + color: alpha(theme.palette.text.primary, 0.6), + opacity: 0.7 + } + }} + /> + + {/* 搜索建议下拉框 - 使用绝对定位确保不被裁剪 */} + {showSuggestions && searchQuery && ( + + + + + {sites.slice(0, 5).map((site, index) => ( + + handleSuggestionClick(site)} + sx={{ + py: 1.5, + '&:hover': { + bgcolor: alpha(theme.palette.primary.main, 0.05) + } + }} + > + + + + + + + {t('datasetSquare.searchVia')} {site.name} Search + + + + + "{searchQuery}" + + + + + } + /> + + + ))} + + + + + )} + + + ); +} diff --git a/easy-dataset-main/components/dataset-square/DatasetSiteCard.js b/easy-dataset-main/components/dataset-square/DatasetSiteCard.js new file mode 100644 index 0000000..0ef45b4 --- /dev/null +++ b/easy-dataset-main/components/dataset-square/DatasetSiteCard.js @@ -0,0 +1,197 @@ +'use client'; + +import { Card, CardActionArea, CardContent, CardMedia, Typography, Box, Chip, useTheme, alpha } from '@mui/material'; +import LaunchIcon from '@mui/icons-material/Launch'; +import StorageIcon from '@mui/icons-material/Storage'; +import { useTranslation } from 'react-i18next'; + +export function DatasetSiteCard({ site }) { + const { name, link, description, image, labels } = site; + const theme = useTheme(); + + // 处理图片路径,如果没有图片则使用默认图片 + const imageUrl = image || `/imgs/default-dataset.png`; + const { t } = useTranslation(); + + // 处理卡片点击 + const handleCardClick = () => { + window.open(link, '_blank'); + }; + + return ( + + + {/* 网站截图 */} + + + + } + label={t('datasetSquare.dataset')} + size="small" + sx={{ + position: 'absolute', + top: 10, + right: 10, + zIndex: 2, + backgroundColor: alpha(theme.palette.background.paper, 0.8), + backdropFilter: 'blur(4px)', + border: `1px solid ${alpha(theme.palette.primary.main, 0.2)}`, + '& .MuiChip-icon': { + color: theme.palette.primary.main + } + }} + /> + + + {/* 网站信息 */} + + + + {name} + + + + + + {description} + + + + {/* 标签显示 */} + {labels && labels.length > 0 && ( + + {labels.map((label, index) => ( + + ))} + + )} + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/dataset-square/DatasetSiteList.js b/easy-dataset-main/components/dataset-square/DatasetSiteList.js new file mode 100644 index 0000000..0f77cab --- /dev/null +++ b/easy-dataset-main/components/dataset-square/DatasetSiteList.js @@ -0,0 +1,211 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Grid, Box, Typography, Skeleton, Divider, Tabs, Tab, Fade, Chip, useTheme, alpha, Paper } from '@mui/material'; +import StorageIcon from '@mui/icons-material/Storage'; +import CategoryIcon from '@mui/icons-material/Category'; +import StarIcon from '@mui/icons-material/Star'; +import { DatasetSiteCard } from './DatasetSiteCard'; +import sites from '@/constant/sites.json'; +import { useTranslation } from 'react-i18next'; + +export function DatasetSiteList() { + const [loading, setLoading] = useState(true); + const theme = useTheme(); + const { t } = useTranslation(); + + // 定义类别 + const CATEGORIES = { + ALL: t('datasetSquare.categories.all'), + POPULAR: t('datasetSquare.categories.popular'), + CHINESE: t('datasetSquare.categories.chinese'), + ENGLISH: t('datasetSquare.categories.english'), + RESEARCH: t('datasetSquare.categories.research'), + MULTIMODAL: t('datasetSquare.categories.multimodal') + }; + const [activeCategory, setActiveCategory] = useState(CATEGORIES.ALL); + + // 模拟加载效果 + useEffect(() => { + const timer = setTimeout(() => { + setLoading(false); + }, 800); + + return () => clearTimeout(timer); + }, []); + + // 处理类别切换 + const handleCategoryChange = (event, newValue) => { + setActiveCategory(newValue); + }; + + // 根据当前选中的类别过滤网站 + const getFilteredSites = () => { + if (activeCategory === CATEGORIES.ALL) { + return sites; + } else if (activeCategory === CATEGORIES.POPULAR) { + return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.popular'))); + } else if (activeCategory === CATEGORIES.CHINESE) { + return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.chinese'))); + } else if (activeCategory === CATEGORIES.ENGLISH) { + return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.english'))); + } else if (activeCategory === CATEGORIES.RESEARCH) { + return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.research'))); + } else if (activeCategory === CATEGORIES.MULTIMODAL) { + return sites.filter(site => site.labels && site.labels.includes(t('datasetSquare.categories.multimodal'))); + } + return sites; + }; + + const filteredSites = getFilteredSites(); + + return ( + + {/* 类别选择器 */} + + + + + {t('datasetSquare.categoryTitle')} + + + + + + } + iconPosition="start" + /> + } + iconPosition="start" + /> + + + + + + + + + {/* 数据集网站列表 */} + + {loading ? ( + // 加载骨架屏 + + {Array.from(new Array(8)).map((_, index) => ( + + + + + + + + + + + + ))} + + ) : ( + + + {/* 结果数量提示 */} + + + {t('datasetSquare.foundResources', { count: filteredSites.length })}{' '} + + + + {activeCategory !== CATEGORIES.ALL && ( + setActiveCategory(CATEGORIES.ALL)} + sx={{ borderRadius: 1.5 }} + /> + )} + + + {filteredSites.length > 0 ? ( + + {filteredSites.map((site, index) => ( + + + + ))} + + ) : ( + + + + {t('datasetSquare.noDatasets')} + + + {t('datasetSquare.tryOtherCategories')} + + + )} + + + )} + + + ); +} diff --git a/easy-dataset-main/components/datasets/DatasetHeader.js b/easy-dataset-main/components/datasets/DatasetHeader.js new file mode 100644 index 0000000..0d88718 --- /dev/null +++ b/easy-dataset-main/components/datasets/DatasetHeader.js @@ -0,0 +1,97 @@ +'use client'; + +import { Box, Button, Divider, Typography, IconButton, CircularProgress, Paper, Tooltip } from '@mui/material'; +import NavigateBeforeIcon from '@mui/icons-material/NavigateBefore'; +import NavigateNextIcon from '@mui/icons-material/NavigateNext'; +import DeleteIcon from '@mui/icons-material/Delete'; +import UndoIcon from '@mui/icons-material/Undo'; +import { useTranslation } from 'react-i18next'; +import { useRouter } from 'next/navigation'; + +/** + * 数据集详情页面的头部导航组件 + */ +export default function DatasetHeader({ + projectId, + datasetsAllCount, + datasetsConfirmCount, + confirming, + unconfirming, + currentDataset, + shortcutsEnabled, + setShortcutsEnabled, + onNavigate, + onConfirm, + onUnconfirm, + onDelete +}) { + const router = useRouter(); + const { t } = useTranslation(); + + return ( + + + + + + {t('datasets.datasetDetail')} + + {t('datasets.stats', { + total: datasetsAllCount, + confirmed: datasetsConfirmCount, + percentage: ((datasetsConfirmCount / datasetsAllCount) * 100).toFixed(2) + })} + + + {/* 快捷键启用选项 - 已注释掉,保持原代码结构 */} + {/* + {t('datasets.enableShortcuts')} + + + ? + + + + */} + + onNavigate('prev')}> + + + onNavigate('next')}> + + + + + {/* 确认/取消确认按钮 */} + {currentDataset.confirmed ? ( + + ) : ( + + )} + + + + + + ); +} diff --git a/easy-dataset-main/components/datasets/DatasetMetadata.js b/easy-dataset-main/components/datasets/DatasetMetadata.js new file mode 100644 index 0000000..8ecdcf8 --- /dev/null +++ b/easy-dataset-main/components/datasets/DatasetMetadata.js @@ -0,0 +1,77 @@ +'use client'; + +import { Box, Typography, Chip, Tooltip, alpha, CircularProgress } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useTheme } from '@mui/material/styles'; +import { useState } from 'react'; + +/** + * 数据集元数据展示组件 + */ +export default function DatasetMetadata({ currentDataset, onViewChunk }) { + const { t } = useTranslation(); + const theme = useTheme(); + + return ( + + + {t('datasets.metadata')} + + + + {currentDataset.questionLabel && ( + + )} + + + { + try { + // 使用新API接口获取文本块内容 + const response = await fetch( + `/api/projects/${currentDataset.projectId}/chunks/name?chunkName=${encodeURIComponent(currentDataset.chunkName)}` + ); + + if (!response.ok) { + throw new Error(`获取文本块失败: ${response.statusText}`); + } + + const chunkData = await response.json(); + + // 调用父组件的方法显示文本块 + onViewChunk({ + name: currentDataset.chunkName, + content: chunkData.content + }); + } catch (error) { + console.error('获取文本块内容失败:', error); + // 即使API请求失败,也尝试调用查看方法 + onViewChunk({ + name: currentDataset.chunkName, + content: '内容加载失败,请重试' + }); + } + }} + sx={{ cursor: 'pointer' }} + /> + + {currentDataset.confirmed && ( + + )} + + + ); +} diff --git a/easy-dataset-main/components/datasets/DatasetRatingSection.js b/easy-dataset-main/components/datasets/DatasetRatingSection.js new file mode 100644 index 0000000..a43ece3 --- /dev/null +++ b/easy-dataset-main/components/datasets/DatasetRatingSection.js @@ -0,0 +1,330 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Box, Typography, Divider, Paper, Button, Stack } from '@mui/material'; +import { toast } from 'sonner'; +import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import StarRating from './StarRating'; +import TagSelector from './TagSelector'; +import NoteInput from './NoteInput'; +import EvalVariantDialog from './EvalVariantDialog'; +import { useTranslation } from 'react-i18next'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; + +/** + * 数据集评分、标签、备注综合组件 + */ +export default function DatasetRatingSection({ dataset, projectId, onUpdate, currentDataset }) { + const { t, i18n } = useTranslation(); + const [availableTags, setAvailableTags] = useState([]); + const [loading, setLoading] = useState(false); + const [addingToEval, setAddingToEval] = useState(false); + const [generatingVariant, setGeneratingVariant] = useState(false); + const [variantDialog, setVariantDialog] = useState({ + open: false, + data: null + }); + + const selectedModel = useAtomValue(selectedModelInfoAtom); + + // 解析数据集中的标签 + const parseDatasetTags = tagsString => { + try { + return JSON.parse(tagsString || '[]'); + } catch (e) { + return []; + } + }; + + // 本地状态管理,从 props 初始化 + const [localScore, setLocalScore] = useState(dataset.score || 0); + const [localTags, setLocalTags] = useState(() => parseDatasetTags(dataset.tags)); + const [localNote, setLocalNote] = useState(dataset.note || ''); + + // 获取项目中已使用的标签 + useEffect(() => { + const fetchAvailableTags = async () => { + try { + const response = await fetch(`/api/projects/${projectId}/datasets/tags`); + if (response.ok) { + const data = await response.json(); + setAvailableTags(data.tags || []); + } + } catch (error) { + console.error('获取可用标签失败:', error); + } + }; + + if (projectId) { + fetchAvailableTags(); + } + }, [projectId]); + + // 同步props中的dataset到本地状态 + useEffect(() => { + setLocalScore(dataset.score || 0); + setLocalTags(parseDatasetTags(dataset.tags)); + setLocalNote(dataset.note || ''); + }, [dataset]); + + // 更新数据集元数据 + const updateMetadata = async updates => { + if (loading) return; + + // 立即更新本地状态,提升响应速度 + if (updates.score !== undefined) { + setLocalScore(updates.score); + } + if (updates.tags !== undefined) { + setLocalTags(updates.tags); + } + if (updates.note !== undefined) { + setLocalNote(updates.note); + } + + setLoading(true); + try { + const response = await fetch(`/api/projects/${projectId}/datasets/${dataset.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(updates) + }); + + if (!response.ok) { + throw new Error('更新失败'); + } + + const result = await response.json(); + + // 显示成功提示 + toast.success(t('datasets.updateSuccess', '更新成功')); + + // 如果有父组件的更新回调,调用它 + if (onUpdate) { + onUpdate(result.dataset); + } + } catch (error) { + console.error('更新数据集元数据失败:', error); + // 显示错误提示 + toast.error(t('datasets.updateFailed', '更新失败')); + + // 出错时恢复本地状态 + if (updates.score !== undefined) { + setLocalScore(dataset.score || 0); + } + if (updates.tags !== undefined) { + setLocalTags(parseDatasetTags(dataset.tags)); + } + if (updates.note !== undefined) { + setLocalNote(dataset.note || ''); + } + } finally { + setLoading(false); + } + }; + + // 处理评分变更 + const handleScoreChange = newScore => { + updateMetadata({ score: newScore }); + }; + + // 处理标签变更 + const handleTagsChange = newTags => { + updateMetadata({ tags: newTags }); + }; + + // 处理备注变更 + const handleNoteChange = newNote => { + updateMetadata({ note: newNote }); + }; + + // 添加到评估数据集 + const handleAddToEval = async () => { + if (addingToEval) return; + + setAddingToEval(true); + try { + const response = await fetch(`/api/projects/${projectId}/datasets/${dataset.id}/copy-to-eval`, { + method: 'POST' + }); + + if (!response.ok) { + throw new Error('Failed to add to eval dataset'); + } + + toast.success(t('datasets.addToEvalSuccess', '成功添加到评估数据集')); + + // 更新本地标签显示 + const currentTags = localTags || []; + if (!currentTags.includes('Eval')) { + setLocalTags([...currentTags, 'Eval']); + } + } catch (error) { + console.error('添加评估数据集失败:', error); + toast.error(t('datasets.addToEvalFailed', '添加失败')); + } finally { + setAddingToEval(false); + } + }; + + // 生成评估集变体 + const handleGenerateEvalVariant = async config => { + if (!selectedModel) { + toast.error(t('datasets.selectModelFirst', '请先选择模型')); + throw new Error('No model selected'); + } + + try { + const language = i18n.language === 'zh-CN' ? 'zh-CN' : 'en'; + const response = await fetch(`/api/projects/${projectId}/datasets/generate-eval-variant`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + datasetId: dataset.id, + model: selectedModel, + language, + questionType: config.questionType, + count: config.count + }) + }); + + if (!response.ok) { + throw new Error('Failed to generate variant'); + } + + const { data } = await response.json(); + + // 为每个生成的项添加题型信息,以便保存时使用 + return Array.isArray(data) ? data.map(item => ({ ...item, questionType: config.questionType })) : []; + } catch (error) { + console.error('生成变体失败:', error); + toast.error(t('datasets.generateVariantFailed', '生成变体失败')); + throw error; + } + }; + + // 保存评估集变体 + const handleSaveEvalVariant = async variantItems => { + try { + // 过滤掉 'Eval' 标签,并确保转为逗号分隔的字符串 + const tagsToSync = (localTags || []).filter(tag => tag !== 'Eval').join(','); + + const itemsToSave = variantItems.map(item => ({ + question: item.question, + correctAnswer: item.correctAnswer, + questionType: item.questionType || 'open_ended', + options: item.options, + tags: tagsToSync, + note: dataset.note, + chunkId: null // 变体暂时不关联原始文本块 + })); + + const response = await fetch(`/api/projects/${projectId}/eval-datasets`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ items: itemsToSave }) + }); + + if (!response.ok) { + throw new Error('Failed to save eval dataset'); + } + + const result = await response.json(); + toast.success(t('datasets.saveVariantSuccess', '已保存到评估数据集')); + + // 关闭对话框 + setVariantDialog({ open: false, data: null }); + } catch (error) { + console.error('保存变体失败:', error); + toast.error(t('datasets.saveVariantFailed', '保存失败')); + } + }; + + return ( + + {/* 评分区域 */} + + + {t('datasets.rating', '评分')} + + + + + + + {/* 标签区域 */} + + + {t('datasets.customTags', '自定义标签')} + + + + + + + {/* 备注区域 */} + + + + + + + + + {currentDataset.aiEvaluation && ( + + + {t('datasets.aiEvaluation')} + + + {currentDataset.aiEvaluation} + + + )} + + setVariantDialog({ open: false, data: null })} + onGenerate={handleGenerateEvalVariant} + onSave={handleSaveEvalVariant} + /> + + ); +} diff --git a/easy-dataset-main/components/datasets/EditableField.js b/easy-dataset-main/components/datasets/EditableField.js new file mode 100644 index 0000000..e1e7c2a --- /dev/null +++ b/easy-dataset-main/components/datasets/EditableField.js @@ -0,0 +1,286 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Box, + Typography, + Button, + TextField, + IconButton, + Switch, + FormControlLabel, + CircularProgress, + Chip +} from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import ReactMarkdown from 'react-markdown'; +import { useTranslation } from 'react-i18next'; +import { useTheme } from '@mui/material/styles'; +import 'github-markdown-css/github-markdown-light.css'; + +function getValue(value, answerType, useMarkdown, t, onOptimize) { + if (value) { + if (answerType === 'custom_format' && onOptimize) { + try { + const data = JSON.parse(value); + value = JSON.stringify(data, null, 2); + return ( + + + {JSON.stringify(data, null, 2)} + + + ); + } catch {} + } + if (answerType === 'label' && onOptimize) { + try { + const labels = JSON.parse(value); + if (Array.isArray(labels)) { + return ( + + {labels.map((label, idx) => ( + + ))} + + ); + } + } catch { + return {value}; + } + } + return useMarkdown ? ( +
+ {value} +
+ ) : ( + {value} + ); + } else { + return ( + + {t('common.noData')} + + ); + } +} + +/** + * 可编辑字段组件,支持 Markdown 和原始文本两种展示方式 + */ +export default function EditableField({ + label, + value, + multiline = true, + editing, + onEdit, + onChange, + onSave, + onCancel, + onOptimize, + tokenCount, + optimizing = false, + dataset +}) { + const { t } = useTranslation(); + const theme = useTheme(); + const { answerType } = dataset; + const custom = answerType === 'custom_format' || answerType === 'label'; + + // 从 localStorage 读取 Markdown 展示设置,默认为 false + const [useMarkdown, setUseMarkdown] = useState(() => { + if (typeof window !== 'undefined') { + const saved = localStorage.getItem('dataset-use-markdown'); + return saved ? JSON.parse(saved) : false; + } + return false; + }); + + // 当 useMarkdown 状态改变时,保存到 localStorage + useEffect(() => { + if (typeof window !== 'undefined') { + localStorage.setItem('dataset-use-markdown', JSON.stringify(useMarkdown)); + } + }, [useMarkdown]); + + const toggleMarkdown = () => { + setUseMarkdown(!useMarkdown); + }; + + const getAnswerTypeLabel = type => { + switch (type) { + case 'label': + return t('imageDatasets.typeLabel', '标签'); + case 'custom_format': + return t('imageDatasets.typeCustom', '自定义'); + default: + return t('imageDatasets.typeText', '文本'); + } + }; + + return ( + + + + {label} + + {!editing && value && ( + <> + {onOptimize && ( + + {getAnswerTypeLabel(answerType)} + + )} + {/* 字符数标签 */} + + {value.length} Characters + + + {/* Token 标签 */} + {tokenCount > 0 && ( + + {tokenCount} Tokens + + )} + + )} + {!editing && ( + <> + + + + {onOptimize && !custom && ( + + {optimizing ? : } + + )} + {!custom && ( + + } + label={{useMarkdown ? 'Markdown' : 'Text'}} + sx={{ ml: 1 }} + /> + )} + + )} + + {editing ? ( + <> + + + + + + + ) : ( + + {getValue(value, answerType, useMarkdown, t, onOptimize)} + + )} + + ); +} diff --git a/easy-dataset-main/components/datasets/EvalVariantDialog.js b/easy-dataset-main/components/datasets/EvalVariantDialog.js new file mode 100644 index 0000000..2b6abdb --- /dev/null +++ b/easy-dataset-main/components/datasets/EvalVariantDialog.js @@ -0,0 +1,238 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + Typography, + Select, + MenuItem, + FormControl, + InputLabel, + Slider, + Card, + CardContent, + IconButton, + CircularProgress +} from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +/** + * 评估集变体编辑对话框 + */ +export default function EvalVariantDialog({ open, onClose, onGenerate, onSave }) { + const { t } = useTranslation(); + const [step, setStep] = useState('config'); // 'config' | 'preview' + const [loading, setLoading] = useState(false); + const [config, setConfig] = useState({ + questionType: 'open_ended', + count: 1 + }); + const [items, setItems] = useState([]); + + // Reset state when dialog opens + useEffect(() => { + if (open) { + setStep('config'); + setConfig({ questionType: 'open_ended', count: 1 }); + setItems([]); + setLoading(false); + } + }, [open]); + + const handleGenerate = async () => { + setLoading(true); + try { + const data = await onGenerate(config); + // Ensure data is an array + const newItems = Array.isArray(data) ? data : [data]; + setItems(newItems); + setStep('preview'); + } catch (error) { + console.error(error); + } finally { + setLoading(false); + } + }; + + const handleSave = () => { + onSave(items); + }; + + const handleItemChange = (index, field, value) => { + const newItems = [...items]; + newItems[index] = { ...newItems[index], [field]: value }; + setItems(newItems); + }; + + const handleDeleteItem = index => { + const newItems = items.filter((_, i) => i !== index); + setItems(newItems); + if (newItems.length === 0) { + setStep('config'); + } + }; + + const renderConfigStep = () => ( + + + {t('datasets.evalVariantConfigHint', '请选择生成的题目类型和数量,AI 将基于当前问答对进行改写。')} + + + + {t('datasets.questionType', '题目类型')} + + + + + + {t('datasets.generateCount', '生成数量')}: {config.count} + + setConfig({ ...config, count: value })} + step={1} + marks + min={1} + max={5} + valueLabelDisplay="auto" + /> + + + ); + + const renderPreviewStep = () => ( + + + {t('datasets.evalVariantPreviewHint', '您可以编辑生成的题目,确认无误后保存到评估集。')} + + + {items.map((item, index) => ( + + + handleDeleteItem(index)} + sx={{ position: 'absolute', right: 8, top: 8 }} + > + + + + + {t('datasets.questionIndex', '题目 {{index}}', { index: index + 1 })} + + + handleItemChange(index, 'question', e.target.value)} + size="small" + /> + + {/* Render Options for choice questions */} + {(item.options || config.questionType.includes('choice')) && ( + { + let val = e.target.value; + try { + // Try to parse if user inputs valid JSON, otherwise keep string + const parsed = JSON.parse(val); + if (Array.isArray(parsed)) val = parsed; + } catch (e) {} + handleItemChange(index, 'options', val); + }} + helperText={t('datasets.optionsHint', '例如: ["选项A", "选项B"]')} + size="small" + /> + )} + + { + let val = e.target.value; + // For multiple choice, answer might be array + if (config.questionType === 'multiple_choice') { + try { + const parsed = JSON.parse(val); + if (Array.isArray(parsed)) val = parsed; + } catch (e) {} + } + handleItemChange(index, 'correctAnswer', val); + }} + helperText={ + config.questionType === 'multiple_choice' + ? t('datasets.answerArrayHint', '多选题答案请输入数组,如 ["A", "C"]') + : config.questionType === 'true_false' + ? t('datasets.answerBoolHint', '判断题答案请输入 ✅ 或 ❌') + : '' + } + size="small" + /> + + + ))} + + ); + + return ( + + + {step === 'config' + ? t('datasets.evalVariantTitle', '生成评估集变体') + : t('datasets.evalVariantPreviewTitle', '确认生成的题目')} + + + {step === 'config' ? renderConfigStep() : renderPreviewStep()} + + + + + {step === 'config' ? ( + + ) : ( + + )} + + + ); +} diff --git a/easy-dataset-main/components/datasets/ImportDatasetDialog.js b/easy-dataset-main/components/datasets/ImportDatasetDialog.js new file mode 100644 index 0000000..c8a425f --- /dev/null +++ b/easy-dataset-main/components/datasets/ImportDatasetDialog.js @@ -0,0 +1,169 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + Stepper, + Step, + StepLabel, + Alert +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import FileUploadStep from './import/FileUploadStep'; +// import DatasetSourceStep from './import/DatasetSourceStep'; // 不再需要 +import FieldMappingStep from './import/FieldMappingStep'; +import ImportProgressStep from './import/ImportProgressStep'; + +/** + * 数据集导入对话框 + */ +export default function ImportDatasetDialog({ open, onClose, projectId, onImportSuccess }) { + const { t } = useTranslation(); + const [importType, setImportType] = useState('file'); // 只支持文件上传 + const [currentStep, setCurrentStep] = useState(0); + const [importData, setImportData] = useState({ + rawData: null, + previewData: null, + fieldMapping: {}, + sourceInfo: null + }); + const [error, setError] = useState(''); + + const steps = [ + t('import.fileUpload', '文件上传'), + t('import.mapFields', '字段映射'), + t('import.importing', '导入中') + ]; + + const handleNext = () => { + setCurrentStep(prev => prev + 1); + }; + + const handleBack = () => { + setCurrentStep(prev => prev - 1); + }; + + const handleClose = () => { + setCurrentStep(0); + setImportData({ + rawData: null, + previewData: null, + fieldMapping: {}, + sourceInfo: null + }); + setError(''); + onClose(); + }; + + const handleDataLoaded = (data, preview, source) => { + setImportData({ + ...importData, + rawData: data, + previewData: preview, + sourceInfo: source + }); + setError(''); + handleNext(); + }; + + const handleFieldMappingComplete = mapping => { + setImportData({ + ...importData, + fieldMapping: mapping + }); + handleNext(); + }; + + const handleImportComplete = () => { + handleClose(); + if (onImportSuccess) { + onImportSuccess(); + } + }; + + const renderStepContent = () => { + switch (currentStep) { + case 0: + return ; + case 1: + return ( + + ); + case 2: + return ( + + ); + default: + return null; + } + }; + + return ( + + {t('import.title', '导入数据集')} + + + {/* 导入类型选择 - 只保留文件上传 */} + + + {t('import.fileUpload', '文件上传')} + + + {t('import.fileUploadDescription', '上传本地文件导入数据集')} + + + + {/* 步骤指示器 */} + + + {steps.map(label => ( + + {label} + + ))} + + + + {/* 错误提示 */} + {error && ( + + {error} + + )} + + {/* 步骤内容 */} + {renderStepContent()} + + + + + {currentStep > 0 && currentStep < 2 && } + + + ); +} diff --git a/easy-dataset-main/components/datasets/NoteInput.js b/easy-dataset-main/components/datasets/NoteInput.js new file mode 100644 index 0000000..e8e9f62 --- /dev/null +++ b/easy-dataset-main/components/datasets/NoteInput.js @@ -0,0 +1,199 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Box, TextField, Typography, IconButton, Tooltip, Collapse } from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import SaveIcon from '@mui/icons-material/Save'; +import CancelIcon from '@mui/icons-material/Cancel'; +import NotesIcon from '@mui/icons-material/Notes'; +import { useTranslation } from 'react-i18next'; + +/** + * 备注输入组件 + */ +export default function NoteInput({ + value = '', + onChange, + placeholder, + readOnly = false, + maxLength = 500, + minRows = 3, + maxRows = 6 +}) { + const { t } = useTranslation(); + const [isEditing, setIsEditing] = useState(false); + const [noteValue, setNoteValue] = useState(value); + const [tempValue, setTempValue] = useState(value); + + // 同步外部value变化 + useEffect(() => { + setNoteValue(value); + setTempValue(value); + }, [value]); + + // 开始编辑 + const handleStartEdit = () => { + setIsEditing(true); + setTempValue(noteValue); + }; + + // 保存备注 + const handleSave = () => { + setNoteValue(tempValue); + setIsEditing(false); + if (onChange) { + onChange(tempValue); + } + }; + + // 取消编辑 + const handleCancel = () => { + setTempValue(noteValue); + setIsEditing(false); + }; + + // 处理键盘快捷键 + const handleKeyDown = event => { + if (event.ctrlKey || event.metaKey) { + if (event.key === 'Enter') { + event.preventDefault(); + handleSave(); + } else if (event.key === 'Escape') { + event.preventDefault(); + handleCancel(); + } + } + }; + + if (readOnly) { + return ( + + + + + {t('datasets.note', '备注')} + + + {noteValue ? ( + + {noteValue} + + ) : ( + + {t('datasets.noNote', '暂无备注')} + + )} + + ); + } + + return ( + + {/* 标题和操作按钮 */} + + + + + {t('datasets.note', '备注')} + + {noteValue && !isEditing && ( + + ({noteValue.length} / {maxLength}) + + )} + + + {!isEditing && ( + + + + + + )} + + + {/* 显示模式 */} + + + {noteValue ? ( + + {noteValue} + + ) : ( + + {placeholder || t('datasets.clickToAddNote', '点击添加备注...')} + + )} + + + + {/* 编辑模式 */} + + + setTempValue(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={placeholder || t('datasets.enterNote', '请输入备注...')} + inputProps={{ maxLength }} + helperText={ + + + {t('datasets.noteShortcuts', 'Ctrl+Enter 保存,Esc 取消')} + + maxLength * 0.9 ? 'warning.main' : 'text.secondary'} + > + {tempValue.length} / {maxLength} + + + } + sx={{ mb: 1 }} + /> + + + + + + + + + maxLength}> + + + + + + + + ); +} diff --git a/easy-dataset-main/components/datasets/OptimizeDialog.js b/easy-dataset-main/components/datasets/OptimizeDialog.js new file mode 100644 index 0000000..b265a9c --- /dev/null +++ b/easy-dataset-main/components/datasets/OptimizeDialog.js @@ -0,0 +1,50 @@ +'use client'; + +import { Dialog, DialogTitle, DialogContent, DialogActions, Button, TextField } from '@mui/material'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +/** + * AI优化对话框组件 + */ +export default function OptimizeDialog({ open, onClose, onConfirm }) { + const [advice, setAdvice] = useState(''); + const { t } = useTranslation(); + + const handleConfirm = () => { + onConfirm(advice); + setAdvice(''); + onClose(); + }; + + const handleClose = () => { + onClose(); + setAdvice(''); + }; + + return ( + + {t('datasets.optimizeTitle')} + + setAdvice(e.target.value)} + placeholder={t('datasets.optimizePlaceholder')} + /> + + + + + + + ); +} diff --git a/easy-dataset-main/components/datasets/StarRating.js b/easy-dataset-main/components/datasets/StarRating.js new file mode 100644 index 0000000..6edfe93 --- /dev/null +++ b/easy-dataset-main/components/datasets/StarRating.js @@ -0,0 +1,69 @@ +'use client'; + +import { useState } from 'react'; +import { Box, Rating, Typography } from '@mui/material'; +import StarIcon from '@mui/icons-material/Star'; +import { useTranslation } from 'react-i18next'; + +/** + * 五星评分组件 + */ +export default function StarRating({ value = 0, onChange, readOnly = false, size = 'medium', showLabel = true }) { + const { t } = useTranslation(); + const [hover, setHover] = useState(-1); + + const labels = { + 0.5: t('rating.veryPoor', '很差'), + 1: t('rating.poor', '差'), + 1.5: t('rating.belowAverage', '偏差'), + 2: t('rating.fair', '一般'), + 2.5: t('rating.average', '中等'), + 3: t('rating.good', '良好'), + 3.5: t('rating.veryGood', '很好'), + 4: t('rating.excellent', '优秀'), + 4.5: t('rating.outstanding', '杰出'), + 5: t('rating.perfect', '完美') + }; + + const getLabelText = value => { + return `${value} Star${value !== 1 ? 's' : ''}, ${labels[value]}`; + }; + + return ( + + { + if (!readOnly && onChange) { + onChange(newValue || 0); + } + }} + onChangeActive={(event, newHover) => { + if (!readOnly) { + setHover(newHover); + } + }} + readOnly={readOnly} + size={size} + icon={} + emptyIcon={} + sx={{ + '& .MuiRating-iconFilled': { + color: '#ffc107' + }, + '& .MuiRating-iconHover': { + color: '#ffb300' + } + }} + /> + {showLabel && ( + + {labels[hover !== -1 ? hover : value] || (value === 0 ? t('rating.unrated', '未评分') : '')} + + )} + + ); +} diff --git a/easy-dataset-main/components/datasets/TagSelector.js b/easy-dataset-main/components/datasets/TagSelector.js new file mode 100644 index 0000000..054bb50 --- /dev/null +++ b/easy-dataset-main/components/datasets/TagSelector.js @@ -0,0 +1,185 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Box, Chip, TextField, Autocomplete, Typography, IconButton, Tooltip } from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import CloseIcon from '@mui/icons-material/Close'; +import { useTranslation } from 'react-i18next'; + +/** + * 标签选择器组件 + * 支持从已有标签选择和自定义添加新标签 + */ +export default function TagSelector({ + value = [], + onChange, + availableTags = [], + placeholder, + readOnly = false, + maxTags = 10 +}) { + const { t } = useTranslation(); + const [inputValue, setInputValue] = useState(''); + + // 确保 value 始终是数组 + const normalizeValue = val => { + if (Array.isArray(val)) { + return val; + } + if (typeof val === 'string') { + try { + const parsed = JSON.parse(val); + return Array.isArray(parsed) ? parsed : []; + } catch { + return []; + } + } + return []; + }; + + const [selectedTags, setSelectedTags] = useState(() => normalizeValue(value)); + + // 同步外部value变化 + useEffect(() => { + setSelectedTags(normalizeValue(value)); + }, [value]); + + // 处理标签变更 + const handleTagsChange = newTags => { + setSelectedTags(newTags); + if (onChange) { + onChange(newTags); + } + }; + + // 添加新标签 + const handleAddTag = newTag => { + if (!newTag || newTag.trim() === '') return; + + const trimmedTag = newTag.trim(); + if (selectedTags.includes(trimmedTag)) return; + + if (selectedTags.length >= maxTags) { + return; + } + + const updatedTags = [...selectedTags, trimmedTag]; + handleTagsChange(updatedTags); + setInputValue(''); + }; + + // 删除标签 + const handleDeleteTag = tagToDelete => { + const updatedTags = selectedTags.filter(tag => tag !== tagToDelete); + handleTagsChange(updatedTags); + }; + + // 处理键盘事件 + const handleKeyPress = event => { + if (event.key === 'Enter' && inputValue.trim()) { + event.preventDefault(); + handleAddTag(inputValue); + } + }; + + // 获取可选的标签选项(排除已选择的) + const getAvailableOptions = () => { + return availableTags.filter(tag => !selectedTags.includes(tag)); + }; + + if (readOnly) { + return ( + + {selectedTags.length > 0 ? ( + selectedTags.map((tag, index) => ( + + )) + ) : ( + + {t('tags.noTags', '暂无标签')} + + )} + + ); + } + + return ( + + {/* 已选择的标签 */} + {selectedTags.length > 0 && ( + + {selectedTags.map((tag, index) => ( + handleDeleteTag(tag)} + deleteIcon={} + /> + ))} + + )} + + {/* 标签输入区域 */} + {selectedTags.length < maxTags && ( + + { + setInputValue(newInputValue); + }} + onChange={(event, newValue) => { + if (newValue) { + handleAddTag(newValue); + } + }} + renderInput={params => ( + + )} + renderOption={(props, option) => ( + + {option} + + )} + sx={{ flexGrow: 1 }} + /> + + + handleAddTag(inputValue)} + disabled={!inputValue.trim()} + color="primary" + > + + + + + )} + + {/* 标签数量提示 */} + {selectedTags.length >= maxTags && ( + + {t('tags.maxTagsReached', `最多可添加 ${maxTags} 个标签`)} + + )} + + {/* 可用标签提示 */} + {availableTags.length > 0 && selectedTags.length < maxTags && ( + + {t('tags.availableTagsHint', '可从已有标签中选择,或输入新标签')} + + )} + + ); +} diff --git a/easy-dataset-main/components/datasets/import/FieldMappingStep.js b/easy-dataset-main/components/datasets/import/FieldMappingStep.js new file mode 100644 index 0000000..ede45ed --- /dev/null +++ b/easy-dataset-main/components/datasets/import/FieldMappingStep.js @@ -0,0 +1,314 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Box, + Typography, + FormControl, + InputLabel, + Select, + MenuItem, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Alert, + Button, + Chip +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +/** + * 字段映射步骤组件 + */ +export default function FieldMappingStep({ previewData, onMappingComplete, onError }) { + const { t } = useTranslation(); + const [fieldMapping, setFieldMapping] = useState({ + question: '', + answer: '', + cot: '', + tags: '' + }); + const [availableFields, setAvailableFields] = useState([]); + const [mappingValid, setMappingValid] = useState(false); + + // 智能字段识别(支持 Alpaca: instruction + input -> question,output -> answer) + const smartFieldMapping = fields => { + const mapping = { + question: '', + answer: '', + cot: '', + tags: '' + }; + + const lower = fields.map(f => f.toLowerCase()); + const instructionIdx = lower.findIndex(f => f.includes('instruction')); + const inputIdx = lower.findIndex(f => f.includes('input')); + const outputIdx = lower.findIndex(f => f.includes('output')); + + // Alpaca 格式的优先识别 + if (instructionIdx !== -1 && inputIdx !== -1) { + // 如果同时有instruction和input字段,将它们组合为question + mapping.question = [fields[instructionIdx], fields[inputIdx]]; + } else if (instructionIdx !== -1) { + // 如果只有instruction字段(比如从ShareGPT转换而来),直接映射为question + mapping.question = fields[instructionIdx]; + } + + if (outputIdx !== -1) { + mapping.answer = fields[outputIdx]; + } + + const questionKeywords = ['question', 'input', 'query', 'prompt', 'instruction', '问题', '输入', '指令']; + const answerKeywords = ['answer', 'output', 'response', 'completion', 'target', '答案', '输出', '回答']; + const cotKeywords = ['cot', 'reasoning', 'explanation', 'thinking', 'rationale', '思维链', '推理', '解释']; + const tagKeywords = ['tag', 'tags', 'label', 'labels', 'category', 'categories', '标签', '类别']; + + fields.forEach(field => { + const fieldLower = field.toLowerCase(); + + if (!mapping.question || (typeof mapping.question === 'string' && !mapping.question)) { + if (questionKeywords.some(keyword => fieldLower.includes(keyword))) { + mapping.question = field; + } + } else if (!mapping.answer) { + if (answerKeywords.some(keyword => fieldLower.includes(keyword))) { + mapping.answer = field; + } + } else if (!mapping.cot) { + if (cotKeywords.some(keyword => fieldLower.includes(keyword))) { + mapping.cot = field; + } + } else if (!mapping.tags) { + if (tagKeywords.some(keyword => fieldLower.includes(keyword))) { + mapping.tags = field; + } + } + }); + + return mapping; + }; + + useEffect(() => { + if (previewData && previewData.length > 0) { + const fields = Object.keys(previewData[0]); + setAvailableFields(fields); + + // 智能识别字段映射 + const smartMapping = smartFieldMapping(fields); + setFieldMapping(smartMapping); + } + }, [previewData]); + + useEffect(() => { + // 验证映射是否有效(问题和答案字段必须选择) + const hasQuestion = Array.isArray(fieldMapping.question) + ? fieldMapping.question.length > 0 + : !!fieldMapping.question; + const hasAnswer = !!fieldMapping.answer; + const isValid = hasQuestion && hasAnswer; + setMappingValid(isValid); + }, [fieldMapping]); + + const handleFieldChange = (targetField, sourceField) => { + setFieldMapping(prev => ({ + ...prev, + [targetField]: + targetField === 'question' + ? Array.isArray(sourceField) + ? sourceField.filter(Boolean) + : sourceField + : sourceField + })); + }; + + const handleConfirmMapping = () => { + if (!mappingValid) { + onError(t('import.mappingRequired', '问题和答案字段为必选项')); + return; + } + + // 检查是否有重复映射(兼容数组) + const flatFields = Object.values(fieldMapping) + .filter(Boolean) + .flatMap(f => (Array.isArray(f) ? f.filter(Boolean) : [f])); + const uniqueFields = [...new Set(flatFields)]; + if (flatFields.length !== uniqueFields.length) { + onError(t('import.duplicateMapping', '不能将多个目标字段映射到同一个源字段')); + return; + } + + onMappingComplete(fieldMapping); + }; + + const getFieldDescription = field => { + switch (field) { + case 'question': + return t('import.questionDesc', '用户的问题或输入内容(必选,可多选)'); + case 'answer': + return t('import.answerDesc', 'AI的回答或输出内容(必选)'); + case 'cot': + return t('import.cotDesc', '思维链或推理过程(可选)'); + case 'tags': + return t('import.tagsDesc', '标签数组,多个标签用逗号分隔(可选)'); + default: + return ''; + } + }; + + const isFieldRequired = field => { + return field === 'question' || field === 'answer'; + }; + + if (!previewData || previewData.length === 0) { + return {t('import.noPreviewData', '没有可预览的数据')}; + } + + return ( + + + {t('import.fieldMapping', '字段映射')} + + + + {t( + 'import.mappingDescription', + '请将源数据的字段映射到目标字段。系统已自动识别可能的映射关系,您可以根据需要调整。' + )} + + + {/* 字段映射选择 */} + + + {t('import.selectMapping', '选择字段映射')} + + + + {Object.keys(fieldMapping).map(targetField => ( + + + {t(`import.${targetField}Field`, targetField)} + {isFieldRequired(targetField) && *} + + {targetField === 'question' ? ( + + ) : ( + + )} + + {getFieldDescription(targetField)} + + + ))} + + + + {/* 数据预览 */} + + + {t('import.dataPreview', '数据预览')} + + {t('import.previewNote', '显示前3条记录,每个字段值最多显示100个字符')} + + + + + + + + {availableFields.map(field => ( + + + {field} + {Object.entries(fieldMapping).map(([targetField, sourceField]) => { + const match = Array.isArray(sourceField) ? sourceField.includes(field) : sourceField === field; + if (match) { + return ( + + ); + } + return null; + })} + + + ))} + + + + {previewData.map((row, index) => ( + + {availableFields.map(field => ( + + + {row[field] || '-'} + + + ))} + + ))} + +
+
+
+ + {/* 确认按钮 */} + + + + + {!mappingValid && ( + + {t('import.requiredFields', '请至少选择问题和答案字段的映射')} + + )} +
+ ); +} diff --git a/easy-dataset-main/components/datasets/import/FileUploadStep.js b/easy-dataset-main/components/datasets/import/FileUploadStep.js new file mode 100644 index 0000000..6d85467 --- /dev/null +++ b/easy-dataset-main/components/datasets/import/FileUploadStep.js @@ -0,0 +1,344 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { + Box, + Typography, + Button, + Paper, + List, + ListItem, + ListItemIcon, + ListItemText, + LinearProgress, + Alert +} from '@mui/material'; +import { CloudUpload as UploadIcon, Description as FileIcon, CheckCircle as CheckIcon } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +// import { useDropzone } from 'react-dropzone'; + +/** + * 文件上传步骤组件 + */ +export default function FileUploadStep({ onDataLoaded, onError }) { + const { t } = useTranslation(); + const [uploading, setUploading] = useState(false); + const [uploadedFiles, setUploadedFiles] = useState([]); + + // 健壮的CSV解析函数,支持多行字段和引号转义 + const parseCSV = text => { + const result = []; + const lines = []; + let currentLine = ''; + let inQuotes = false; + + // 逐字符解析,正确处理引号内的换行符 + for (let i = 0; i < text.length; i++) { + const char = text[i]; + const nextChar = text[i + 1]; + + if (char === '"') { + if (inQuotes && nextChar === '"') { + // 转义的引号 + currentLine += '"'; + i++; // 跳过下一个引号 + } else { + // 切换引号状态 + inQuotes = !inQuotes; + } + } else if (char === '\n' && !inQuotes) { + // 行结束(不在引号内) + if (currentLine.trim()) { + lines.push(currentLine); + } + currentLine = ''; + } else { + currentLine += char; + } + } + + // 添加最后一行 + if (currentLine.trim()) { + lines.push(currentLine); + } + + if (lines.length < 2) { + throw new Error('CSV文件格式不正确,至少需要标题行和一行数据'); + } + + // 解析标题行 + const headers = parseCSVLine(lines[0]); + + // 解析数据行 + for (let i = 1; i < lines.length; i++) { + const values = parseCSVLine(lines[i]); + if (values.length > 0) { + const obj = {}; + headers.forEach((header, index) => { + obj[header] = values[index] || ''; + }); + result.push(obj); + } + } + + return result; + }; + + // 解析单行CSV,处理逗号分隔和引号转义 + const parseCSVLine = line => { + const result = []; + let current = ''; + let inQuotes = false; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + const nextChar = line[i + 1]; + + if (char === '"') { + if (inQuotes && nextChar === '"') { + // 转义的引号 + current += '"'; + i++; // 跳过下一个引号 + } else { + // 切换引号状态 + inQuotes = !inQuotes; + } + } else if (char === ',' && !inQuotes) { + // 字段分隔符(不在引号内) + result.push(current.trim()); + current = ''; + } else { + current += char; + } + } + + // 添加最后一个字段 + result.push(current.trim()); + + return result; + }; + + // 检测并转换ShareGPT格式为Alpaca格式 + const convertShareGPTToAlpaca = item => { + // 检查是否包含conversations字段且格式正确 + if (item.conversations && Array.isArray(item.conversations)) { + const conversations = item.conversations; + + // 查找system、human、gpt消息 + let systemMessage = ''; + let instruction = ''; + let output = ''; + + for (const conv of conversations) { + if (conv.from === 'system' && conv.value) { + systemMessage = conv.value; + } else if (conv.from === 'human' && conv.value) { + instruction = conv.value; + } else if (conv.from === 'gpt' && conv.value) { + output = conv.value; + break; // 只取第一轮对话 + } + } + + // 如果有system消息,将其作为instruction的前缀 + if (systemMessage && instruction) { + instruction = `${systemMessage}\n\n${instruction}`; + } else if (systemMessage && !instruction) { + instruction = systemMessage; + } + + // 转换为Alpaca格式 + return { + instruction: instruction || '', + input: '', // ShareGPT格式通常没有单独的input字段 + output: output || '', + // 保留其他字段 + ...Object.fromEntries(Object.entries(item).filter(([key]) => key !== 'conversations')) + }; + } + + return item; // 如果不是ShareGPT格式,返回原始数据 + }; + + const parseFileContent = async file => { + const text = await file.text(); + const extension = file.name.split('.').pop().toLowerCase(); + + try { + let data = []; + + if (extension === 'json') { + const parsed = JSON.parse(text); + data = Array.isArray(parsed) ? parsed : [parsed]; + } else if (extension === 'jsonl') { + data = text + .split('\n') + .filter(line => line.trim()) + .map(line => JSON.parse(line)); + } else if (extension === 'csv') { + // 更健壮的CSV解析,支持多行字段和引号转义 + data = parseCSV(text); + if (data.length === 0) { + throw new Error('CSV文件格式不正确或没有数据'); + } + } else { + throw new Error('不支持的文件格式'); + } + + if (data.length === 0) { + throw new Error('文件中没有找到有效数据'); + } + + // 检测并转换ShareGPT格式为Alpaca格式 + data = data.map(convertShareGPTToAlpaca); + + // 生成预览数据(取前3条记录,每个字段值截取前100字符) + const previewData = data.slice(0, 3).map(item => { + const preview = {}; + Object.keys(item).forEach(key => { + const value = String(item[key] || ''); + preview[key] = value.length > 100 ? value.substring(0, 100) + '...' : value; + }); + return preview; + }); + + return { + data, + preview: previewData, + source: { + type: 'file', + fileName: file.name, + fileSize: file.size, + totalRecords: data.length + } + }; + } catch (error) { + throw new Error(`解析文件失败: ${error.message}`); + } + }; + + const handleFileSelect = async event => { + const files = event.target.files; + if (!files || files.length === 0) return; + + const file = files[0]; + setUploading(true); + + try { + const result = await parseFileContent(file); + setUploadedFiles([ + { + name: file.name, + size: file.size, + status: 'success' + } + ]); + + onDataLoaded(result.data, result.preview, result.source); + } catch (error) { + setUploadedFiles([ + { + name: file.name, + size: file.size, + status: 'error', + error: error.message + } + ]); + onError(error.message); + } finally { + setUploading(false); + } + }; + + const formatFileSize = bytes => { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }; + + return ( + + + {t('import.uploadFile', '上传文件')} + + + + {t('import.supportedFormats', '支持 JSON、JSONL、CSV 格式文件')} + + + {/* 文件上传区域 */} + document.getElementById('file-upload-input').click()} + > + + + + {t('import.dragDropFile', '拖拽文件到此处或点击选择文件')} + + + {t('import.maxFileSize', '最大文件大小: 50MB')} + + + + {/* 上传进度 */} + {uploading && ( + + + {t('import.processingFile', '正在处理文件...')} + + + + )} + + {/* 已上传文件列表 */} + {uploadedFiles.length > 0 && ( + + + {t('import.uploadedFiles', '已上传文件')} + + + {uploadedFiles.map((file, index) => ( + + + {file.status === 'success' ? : } + + + + ))} + + + {uploadedFiles.some(f => f.status === 'error') && ( + + {t('import.uploadError', '文件上传失败,请检查文件格式是否正确')} + + )} + + )} + + ); +} diff --git a/easy-dataset-main/components/datasets/import/ImportProgressStep.js b/easy-dataset-main/components/datasets/import/ImportProgressStep.js new file mode 100644 index 0000000..28bb6a9 --- /dev/null +++ b/easy-dataset-main/components/datasets/import/ImportProgressStep.js @@ -0,0 +1,303 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { + Box, + Typography, + LinearProgress, + Alert, + Paper, + List, + ListItem, + ListItemIcon, + ListItemText, + Chip +} from '@mui/material'; +import { CheckCircle as CheckIcon, Error as ErrorIcon, Info as InfoIcon } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; + +/** + * 导入进度步骤组件 + */ +export default function ImportProgressStep({ projectId, rawData, fieldMapping, sourceInfo, onComplete, onError }) { + const { t } = useTranslation(); + const [progress, setProgress] = useState(0); + const [currentStep, setCurrentStep] = useState(''); + const [importStats, setImportStats] = useState({ + total: 0, + processed: 0, + success: 0, + failed: 0, + skipped: 0, + errors: [] + }); + const [completed, setCompleted] = useState(false); + const startedRef = useRef(false); // 防止在开发模式下因严格模式导致重复执行 + + useEffect(() => { + if (!startedRef.current && rawData && fieldMapping && projectId) { + startedRef.current = true; + startImport(); + } + }, [rawData, fieldMapping, projectId]); + + const startImport = async () => { + try { + setCurrentStep(t('import.preparingData', '准备数据...')); + setImportStats(prev => ({ ...prev, total: rawData.length })); + + // 转换数据格式 + const convertedData = rawData.map(item => { + // 支持 question 映射多个字段,拼接为一个字符串 + const qFields = fieldMapping.question; + const question = Array.isArray(qFields) + ? qFields + .map(f => item[f] || '') + .filter(v => v && String(v).trim()) + .join('\n') + : item[qFields] || ''; + + const converted = { + question, + answer: item[fieldMapping.answer] || '', + cot: fieldMapping.cot ? item[fieldMapping.cot] || '' : '', + questionLabel: '', // 默认标签,后续可以通过AI生成 + chunkName: sourceInfo?.datasetName || sourceInfo?.fileName || 'Imported Data', + chunkContent: `Imported from ${sourceInfo?.type || 'file'}`, + model: 'imported', + confirmed: false, + score: 0, + tags: fieldMapping.tags ? JSON.stringify(parseTagsField(item[fieldMapping.tags])) : '[]', + note: '', + other: JSON.stringify(getOtherFields(item, fieldMapping)) + }; + + // 不在前端抛错,由后端负责校验并统计 skipped + return converted; + }); + + setProgress(25); + setCurrentStep(t('import.uploadingData', '上传数据...')); + + // 分批上传数据 + const batchSize = 500; + let processed = 0; + let success = 0; + let failed = 0; + let skipped = 0; + const errors = []; + + for (let i = 0; i < convertedData.length; i += batchSize) { + const batch = convertedData.slice(i, i + batchSize); + + try { + const response = await fetch(`/api/projects/${projectId}/datasets/import`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + datasets: batch, + sourceInfo + }) + }); + + if (!response.ok) { + throw new Error(`批次上传失败: ${response.statusText}`); + } + + const result = await response.json(); + success += result.success || 0; + failed += typeof result.failed === 'number' ? result.failed : result.errors?.length || 0; + skipped += result.skipped || 0; + processed += batch.length; + + if (result.errors && result.errors.length > 0) { + errors.push(...result.errors); + } + } catch (error) { + failed += batch.length; + processed += batch.length; + errors.push(`批次 ${Math.floor(i / batchSize) + 1}: ${error.message}`); + } + + // 更新进度 + const progressPercent = 25 + (processed / convertedData.length) * 70; + setProgress(progressPercent); + setImportStats({ + total: convertedData.length, + processed, + success, + failed, + skipped, + errors + }); + + setCurrentStep( + t('import.processing', '处理中... {{processed}}/{{total}}', { + processed, + total: convertedData.length + }) + ); + } + + setProgress(100); + setCurrentStep(t('import.completed', '导入完成')); + setCompleted(true); + + // 延迟一下再调用完成回调,让用户看到完成状态 + setTimeout(() => { + onComplete(); + }, 2000); + } catch (error) { + onError(error.message); + setImportStats(prev => ({ + ...prev, + errors: [...prev.errors, error.message] + })); + } + }; + + // 解析标签字段 + const parseTagsField = tagsValue => { + if (!tagsValue) return []; + + if (Array.isArray(tagsValue)) { + return tagsValue; + } + + if (typeof tagsValue === 'string') { + return tagsValue + .split(',') + .map(tag => tag.trim()) + .filter(tag => tag); + } + + return []; + }; + + // 获取其他字段(兼容数组映射) + const getOtherFields = (item, mapping) => { + const used = []; + Object.values(mapping).forEach(field => { + if (!field) return; + if (Array.isArray(field)) used.push(...field); + else used.push(field); + }); + const mappedFields = new Set(used); + const otherFields = {}; + + Object.keys(item).forEach(key => { + if (!mappedFields.has(key)) { + otherFields[key] = item[key]; + } + }); + + return otherFields; + }; + + return ( + + + {t('import.importing', '正在导入数据集')} + + + {/* 进度条 */} + + + {currentStep} + + + + {Math.round(progress)}% {t('import.complete', '完成')} + + + + {/* 导入统计 */} + + + {t('import.importStats', '导入统计')} + + + + } + label={t('import.total', '总计: {{count}}', { count: importStats.total })} + variant="outlined" + /> + } + label={t('import.success', '成功: {{count}}', { count: importStats.success })} + color="success" + variant="outlined" + /> + {importStats.skipped > 0 && ( + } + label={t('import.skipped', '跳过: {{count}}', { count: importStats.skipped })} + color="warning" + variant="outlined" + /> + )} + {importStats.failed > 0 && ( + } + label={t('import.failed', '失败: {{count}}', { count: importStats.failed })} + color="error" + variant="outlined" + /> + )} + + + {sourceInfo && ( + + + {t('import.source', '数据源')}:{' '} + {sourceInfo.type === 'file' ? sourceInfo.fileName : sourceInfo.datasetName} + + {sourceInfo.description && ( + + {t('import.description', '描述')}: {sourceInfo.description} + + )} + + )} + + + {/* 错误列表 */} + {importStats.errors.length > 0 && ( + + + {t('import.errors', '错误信息')} + + + {importStats.errors.slice(0, 10).map((error, index) => ( + + + + + + + ))} + + {importStats.errors.length > 10 && ( + + {t('import.moreErrors', '还有 {{count}} 个错误未显示...', { + count: importStats.errors.length - 10 + })} + + )} + + )} + + {/* 完成提示 */} + {completed && ( + + {t('import.importSuccess', '数据集导入完成!成功导入 {{success}} 条记录。', { + success: importStats.success + })} + + )} + + ); +} diff --git a/easy-dataset-main/components/datasets/utils/ratingUtils.js b/easy-dataset-main/components/datasets/utils/ratingUtils.js new file mode 100644 index 0000000..9af83a1 --- /dev/null +++ b/easy-dataset-main/components/datasets/utils/ratingUtils.js @@ -0,0 +1,135 @@ +/** + * 评分相关的工具函数 + */ + +/** + * 根据评分获取对应的颜色和标签(不包含国际化) + * @param {number} score - 评分 (0-5) + * @returns {object} - 包含颜色、背景色和标签的对象 + */ +export const getRatingConfig = score => { + if (score >= 4.5) { + return { + color: '#2e7d32', // 深绿色 + backgroundColor: '#e8f5e8', + label: '优秀', + variant: 'excellent' + }; + } else if (score >= 3.5) { + return { + color: '#388e3c', // 绿色 + backgroundColor: '#f1f8e9', + label: '良好', + variant: 'good' + }; + } else if (score >= 2.5) { + return { + color: '#f57c00', // 橙色 + backgroundColor: '#fff3e0', + label: '一般', + variant: 'average' + }; + } else if (score >= 1.5) { + return { + color: '#f44336', // 红色 + backgroundColor: '#ffebee', + label: '较差', + variant: 'poor' + }; + } else if (score > 0) { + return { + color: '#d32f2f', // 深红色 + backgroundColor: '#ffebee', + label: '很差', + variant: 'very-poor' + }; + } else { + return { + color: '#757575', // 灰色 + backgroundColor: '#f5f5f5', + label: '未评分', + variant: 'unrated' + }; + } +}; + +/** + * 根据评分获取对应的颜色和国际化标签 + * @param {number} score - 评分 (0-5) + * @param {function} t - 国际化翻译函数 + * @returns {object} - 包含颜色、背景色和国际化标签的对象 + */ +export const getRatingConfigI18n = (score, t) => { + const baseConfig = getRatingConfig(score); + + // 根据variant获取对应的翻译键 + let translationKey; + let fallbackText; + + switch (baseConfig.variant) { + case 'excellent': + translationKey = 'datasets.ratingExcellent'; + fallbackText = '优秀'; + break; + case 'good': + translationKey = 'datasets.ratingGood'; + fallbackText = '良好'; + break; + case 'average': + translationKey = 'datasets.ratingAverage'; + fallbackText = '一般'; + break; + case 'poor': + translationKey = 'datasets.ratingPoor'; + fallbackText = '较差'; + break; + case 'very-poor': + translationKey = 'datasets.ratingVeryPoor'; + fallbackText = '很差'; + break; + case 'unrated': + translationKey = 'datasets.ratingUnrated'; + fallbackText = '未评分'; + break; + default: + translationKey = 'datasets.ratingUnrated'; + fallbackText = '未评分'; + } + + return { + ...baseConfig, + label: t(translationKey, fallbackText) + }; +}; + +/** + * 格式化评分显示 + * @param {number} score - 评分 + * @returns {string} - 格式化后的评分字符串 + */ +export const formatScore = score => { + if (score === 0) return ''; + return score.toFixed(1); +}; + +/** + * 获取评分范围的描述 + * @param {number} score - 评分 + * @returns {string} - 评分范围描述 + */ +export const getScoreDescription = score => { + const config = getRatingConfig(score); + return `${formatScore(score)} - ${config.label}`; +}; + +/** + * 评分范围常量 + */ +export const SCORE_RANGES = { + EXCELLENT: { min: 4.5, max: 5.0, label: '优秀' }, + GOOD: { min: 3.5, max: 4.4, label: '良好' }, + AVERAGE: { min: 2.5, max: 3.4, label: '一般' }, + POOR: { min: 1.5, max: 2.4, label: '较差' }, + VERY_POOR: { min: 0.1, max: 1.4, label: '很差' }, + UNRATED: { min: 0, max: 0, label: '未评分' } +}; diff --git a/easy-dataset-main/components/distill/AutoDistillDialog.js b/easy-dataset-main/components/distill/AutoDistillDialog.js new file mode 100644 index 0000000..3f45a7f --- /dev/null +++ b/easy-dataset-main/components/distill/AutoDistillDialog.js @@ -0,0 +1,325 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + Typography, + Box, + Alert, + Paper, + Divider, + FormControl, + FormLabel, + RadioGroup, + FormControlLabel, + Radio +} from '@mui/material'; + +/** + * 全自动蒸馏数据集配置弹框 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调 + * @param {Function} props.onStart - 开始蒸馏任务的回调 + * @param {Function} props.onStartBackground - 开始后台蒸馏任务的回调 + * @param {string} props.projectId - 项目ID + * @param {Object} props.project - 项目信息 + * @param {Object} props.stats - 当前统计信息 + */ +export default function AutoDistillDialog({ + open, + onClose, + onStart, + onStartBackground, + projectId, + project, + stats = {} +}) { + const { t } = useTranslation(); + + // 表单状态 + const [topic, setTopic] = useState(''); + const [levels, setLevels] = useState(2); + const [tagsPerLevel, setTagsPerLevel] = useState(10); + const [questionsPerTag, setQuestionsPerTag] = useState(10); + const [datasetType, setDatasetType] = useState('single-turn'); // 'single-turn' | 'multi-turn' | 'both' + + // 计算信息 + const [estimatedTags, setEstimatedTags] = useState(0); // 所有标签总数(包括根节点和中间节点) + const [leafTags, setLeafTags] = useState(0); // 叶子节点数量(即最后一层标签数) + const [estimatedQuestions, setEstimatedQuestions] = useState(0); + const [newTags, setNewTags] = useState(0); + const [newQuestions, setNewQuestions] = useState(0); + const [error, setError] = useState(''); + + // 初始化默认主题 + useEffect(() => { + if (project && project.name) { + setTopic(project.name); + } + }, [project]); + + // 计算预估标签和问题数量 + useEffect(() => { + /* + * 根据公式:总问题数 = \left( \prod_{i=1}^{n} L_i \right) \times Q + * 当每层标签数量相同(L)时:总问题数 = L^n \times Q + */ + + const leafTags = Math.pow(tagsPerLevel, levels); + + // 总问题数 = 叶子节点数 * 每个节点的问题数 + const totalQuestions = leafTags * questionsPerTag; + + let totalTags; + if (tagsPerLevel === 1) { + // 如果每层只有1个标签,总数就是 levels+1 + totalTags = levels + 1; + } else { + // 使用等比数列求和公式 + totalTags = (1 - Math.pow(tagsPerLevel, levels + 1)) / (1 - tagsPerLevel); + } + + setLeafTags(leafTags); + setEstimatedTags(leafTags); // 改为只显示叶子节点数量,而非所有节点数量 + setEstimatedQuestions(totalQuestions); + + // 计算新增标签和问题数量 + const currentTags = stats.tagsCount || 0; + const currentQuestions = stats.questionsCount || 0; + + // 只考虑最后一层的标签数量 + setNewTags(Math.max(0, leafTags - currentTags)); + setNewQuestions(Math.max(0, totalQuestions - currentQuestions)); + + // 验证是否可以执行任务 + if (leafTags <= currentTags && totalQuestions <= currentQuestions) { + setError(t('distill.autoDistillInsufficientError')); + } else { + setError(''); + } + }, [levels, tagsPerLevel, questionsPerTag, stats, t]); + + // 处理开始任务 + const handleStart = () => { + if (error) return; + + onStart({ + topic, + levels, + tagsPerLevel, + questionsPerTag, + estimatedTags, + estimatedQuestions, + datasetType + }); + }; + + // 处理开始后台任务 + const handleStartBackground = () => { + if (error) return; + + onStartBackground({ + topic, + levels, + tagsPerLevel, + questionsPerTag, + estimatedTags, + estimatedQuestions, + datasetType + }); + }; + + return ( + + {t('distill.autoDistillTitle')} + + + {/* 左侧:输入区域 */} + + setTopic(e.target.value)} + fullWidth + margin="normal" + required + disabled + helperText={t('distill.rootTopicHelperText')} + /> + + + {t('distill.tagLevels')} + { + const value = Math.min(5, Math.max(1, Number(e.target.value))); + setLevels(value); + }} + helperText={t('distill.tagLevelsHelper', { max: 5 })} + /> + + + + {t('distill.tagsPerLevel')} + { + const value = Math.min(50, Math.max(1, Number(e.target.value))); + setTagsPerLevel(value); + }} + helperText={t('distill.tagsPerLevelHelper', { max: 50 })} + /> + + + + {t('distill.questionsPerTag')} + { + const value = Math.min(50, Math.max(1, Number(e.target.value))); + setQuestionsPerTag(value); + }} + helperText={t('distill.questionsPerTagHelper', { max: 50 })} + /> + + + + + + {t('distill.datasetType', { defaultValue: '数据集类型' })} + + setDatasetType(e.target.value)}> + } + label={t('distill.singleTurnDataset', { defaultValue: '单轮对话数据集' })} + /> + } + label={t('distill.multiTurnDataset', { defaultValue: '多轮对话数据集' })} + /> + } + label={t('distill.bothDatasetTypes', { defaultValue: '两种数据集都生成' })} + /> + + + + + + {/* 右侧:预估信息区域 */} + + + + {t('distill.estimationInfo')} + + + + + + {t('distill.estimatedTags')}: + + {estimatedTags} + + + + + {t('distill.estimatedQuestions')}: + + {estimatedQuestions} + + + + + + + {t('distill.currentTags')}: + + {stats.tagsCount || 0} + + + + + {t('distill.currentQuestions')}: + + {stats.questionsCount || 0} + + + + + + + + {t('distill.newTags')}: + + + {newTags} + + + + + + {t('distill.newQuestions')}: + + + {newQuestions} + + + + + + + {error && ( + + {error} + + )} + + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/distill/AutoDistillProgress.js b/easy-dataset-main/components/distill/AutoDistillProgress.js new file mode 100644 index 0000000..2207e4f --- /dev/null +++ b/easy-dataset-main/components/distill/AutoDistillProgress.js @@ -0,0 +1,212 @@ +'use client'; + +import { useState, useEffect, useRef } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogContent, + Box, + Typography, + LinearProgress, + Paper, + Divider, + IconButton, + Button +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; + +/** + * 全自动蒸馏进度组件 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调 + * @param {Object} props.progress - 进度信息 + */ +export default function AutoDistillProgress({ open, onClose, progress = {} }) { + const { t } = useTranslation(); + const logContainerRef = useRef(null); + + // 自动滚动到底部 + useEffect(() => { + if (logContainerRef.current) { + logContainerRef.current.scrollTop = logContainerRef.current.scrollHeight; + } + }, [progress.logs]); + + const getStageText = () => { + const { stage } = progress; + switch (stage) { + case 'level1': + return t('distill.stageBuildingLevel1'); + case 'level2': + return t('distill.stageBuildingLevel2'); + case 'level3': + return t('distill.stageBuildingLevel3'); + case 'level4': + return t('distill.stageBuildingLevel4'); + case 'level5': + return t('distill.stageBuildingLevel5'); + case 'questions': + return t('distill.stageBuildingQuestions'); + case 'datasets': + return t('distill.stageBuildingDatasets'); + case 'multi-turn-datasets': + return t('distill.stageBuildingMultiTurnDatasets', { defaultValue: '生成多轮对话数据集中...' }); + case 'completed': + return t('distill.stageCompleted'); + default: + return t('distill.stageInitializing'); + } + }; + + const getOverallProgress = () => { + const { tagsBuilt, tagsTotal, questionsBuilt, questionsTotal, datasetsBuilt, datasetsTotal } = progress; + + // 整体进度按比例计算:标签构建占30%,问题生成占35%,数据集生成占35% + let tagProgress = tagsTotal ? (tagsBuilt / tagsTotal) * 30 : 0; + let questionProgress = questionsTotal ? (questionsBuilt / questionsTotal) * 35 : 0; + let datasetProgress = datasetsTotal ? (datasetsBuilt / datasetsTotal) * 35 : 0; + + return Math.min(100, Math.round(tagProgress + questionProgress + datasetProgress)); + }; + + return ( + + + + {t('distill.autoDistillProgress')} + {(progress.stage === 'completed' || !progress.stage) && ( + + + + )} + + + + + {/* 整体进度 */} + + + {t('distill.overallProgress')} + + + + + + + {getOverallProgress()}% + + + + + 0 ? 'repeat(4, 1fr)' : 'repeat(3, 1fr)', + gap: 2 + }} + > + + + {t('distill.tagsProgress')} + + + {progress.tagsBuilt || 0} / {progress.tagsTotal || 0} + + + + + + {t('distill.questionsProgress')} + + + {progress.questionsBuilt || 0} / {progress.questionsTotal || 0} + + + + + + {t('distill.datasetsProgress')} + + + {progress.datasetsBuilt || 0} / {progress.datasetsTotal || 0} + + + + {progress.multiTurnDatasetsTotal > 0 && ( + + + {t('distill.multiTurnDatasetsProgress', { defaultValue: '多轮对话进度' })} + + + {progress.multiTurnDatasetsBuilt || 0} / {progress.multiTurnDatasetsTotal || 0} + + + )} + + + + {/* 当前阶段 */} + + + {t('distill.currentStage')} + + + + {getStageText()} + + + + {/* 实时日志 */} + + + {t('distill.realTimeLogs')} + + + + {progress.logs?.length > 0 ? ( + progress.logs.map((log, index) => { + // 检测成功日志,显示为绿色 Successfully + let color = 'inherit'; + if (log.includes('成功') || log.includes('完成') || log.includes('Successfully')) { + color = '#4caf50'; + } + if (log.includes('失败') || log.toLowerCase().includes('error')) { + color = '#f44336'; + } + return ( + + {log} + + ); + }) + ) : ( + + {t('distill.waitingForLogs')} + + )} + + + + + + ); +} diff --git a/easy-dataset-main/components/distill/ConfirmDialog.js b/easy-dataset-main/components/distill/ConfirmDialog.js new file mode 100644 index 0000000..2180b9a --- /dev/null +++ b/easy-dataset-main/components/distill/ConfirmDialog.js @@ -0,0 +1,37 @@ +'use client'; + +import { Dialog, DialogActions, DialogTitle, Button } from '@mui/material'; + +/** + * 通用确认对话框组件 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调 + * @param {Function} props.onConfirm - 确认操作的回调 + * @param {string} props.title - 对话框标题 + * @param {string} props.cancelText - 取消按钮文本 + * @param {string} props.confirmText - 确认按钮文本 + */ +export default function ConfirmDialog({ + open, + onClose, + onConfirm, + title, + cancelText = '取消', + confirmText = '确认', + confirmColor = 'error' +}) { + return ( + + {title} + + + + + + ); +} diff --git a/easy-dataset-main/components/distill/DistillTreeView.js b/easy-dataset-main/components/distill/DistillTreeView.js new file mode 100644 index 0000000..12cd6f2 --- /dev/null +++ b/easy-dataset-main/components/distill/DistillTreeView.js @@ -0,0 +1,515 @@ +'use client'; + +import { useState, useEffect, useCallback, useMemo, forwardRef, useImperativeHandle } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Box, Typography, List } from '@mui/material'; +import axios from 'axios'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; +import { useGenerateDataset } from '@/hooks/useGenerateDataset'; +import { toast } from 'sonner'; + +// 导入子组件 +import TagTreeItem from './TagTreeItem'; +import TagMenu from './TagMenu'; +import TagEditDialog from './TagEditDialog'; +import ConfirmDialog from './ConfirmDialog'; +import { sortTagsByNumber } from './utils'; + +/** + * 蒸馏树形视图组件 + * @param {Object} props + * @param {string} props.projectId - 项目ID + * @param {Array} props.tags - 标签列表 + * @param {Function} props.onGenerateSubTags - 生成子标签的回调函数 + * @param {Function} props.onGenerateQuestions - 生成问题的回调函数 + * @param {Function} props.onTagsUpdate - 标签更新的回调函数 + */ +const DistillTreeView = forwardRef(function DistillTreeView( + { projectId, tags = [], onGenerateSubTags, onGenerateQuestions, onTagsUpdate }, + ref +) { + const { t } = useTranslation(); + const selectedModel = useAtomValue(selectedModelInfoAtom); + const [expandedTags, setExpandedTags] = useState({}); + const [tagQuestions, setTagQuestions] = useState({}); + const [loadingTags, setLoadingTags] = useState({}); + const [loadingQuestions, setLoadingQuestions] = useState({}); + const [menuAnchorEl, setMenuAnchorEl] = useState(null); + const [selectedTagForMenu, setSelectedTagForMenu] = useState(null); + const [allQuestions, setAllQuestions] = useState([]); + const [loading, setLoading] = useState(false); + const [processingQuestions, setProcessingQuestions] = useState({}); + const [processingMultiTurnQuestions, setProcessingMultiTurnQuestions] = useState({}); + const [deleteQuestionConfirmOpen, setDeleteQuestionConfirmOpen] = useState(false); + const [questionToDelete, setQuestionToDelete] = useState(null); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [tagToDelete, setTagToDelete] = useState(null); + const [editDialogOpen, setEditDialogOpen] = useState(false); + const [tagToEdit, setTagToEdit] = useState(null); + const [project, setProject] = useState(null); + const [projectName, setProjectName] = useState(''); + + // 使用生成数据集的hook + const { generateSingleDataset } = useGenerateDataset(); + + // 获取问题统计信息 + const fetchQuestionsStats = useCallback(async () => { + try { + setLoading(true); + const response = await axios.get(`/api/projects/${projectId}/questions/tree?isDistill=true`); + setAllQuestions(response.data); + console.log('获取问题统计信息成功:', { totalQuestions: response.data.length }); + } catch (error) { + console.error('获取问题统计信息失败:', error); + } finally { + setLoading(false); + } + }, [projectId]); + + // 暴露方法给父组件 + useImperativeHandle(ref, () => ({ + fetchQuestionsStats + })); + + // 获取标签下的问题 + const fetchQuestionsByTag = useCallback( + async tagId => { + try { + setLoadingQuestions(prev => ({ ...prev, [tagId]: true })); + const response = await axios.get(`/api/projects/${projectId}/distill/questions/by-tag?tagId=${tagId}`); + setTagQuestions(prev => ({ + ...prev, + [tagId]: response.data + })); + } catch (error) { + console.error('获取标签问题失败:', error); + } finally { + setLoadingQuestions(prev => ({ ...prev, [tagId]: false })); + } + }, + [projectId] + ); + + // 获取项目信息,获取项目名称 + useEffect(() => { + if (projectId) { + axios + .get(`/api/projects/${projectId}`) + .then(response => { + setProject(response.data); + setProjectName(response.data.name || ''); + }) + .catch(error => { + console.error('获取项目信息失败:', error); + }); + } + }, [projectId]); + + // 初始化时获取问题统计信息 + useEffect(() => { + fetchQuestionsStats(); + }, [fetchQuestionsStats]); + + // 构建标签树 + const tagTree = useMemo(() => { + const rootTags = []; + const tagMap = {}; + + // 创建标签映射 + tags.forEach(tag => { + tagMap[tag.id] = { ...tag, children: [] }; + }); + + // 构建树结构 + tags.forEach(tag => { + if (tag.parentId && tagMap[tag.parentId]) { + tagMap[tag.parentId].children.push(tagMap[tag.id]); + } else { + rootTags.push(tagMap[tag.id]); + } + }); + + return rootTags; + }, [tags]); + + // 切换标签展开/折叠状态 + const toggleTag = useCallback( + tagId => { + setExpandedTags(prev => ({ + ...prev, + [tagId]: !prev[tagId] + })); + + // 如果展开且还没有加载过问题,则加载问题 + if (!expandedTags[tagId] && !tagQuestions[tagId]) { + fetchQuestionsByTag(tagId); + } + }, + [expandedTags, tagQuestions, fetchQuestionsByTag] + ); + + // 处理菜单打开 + const handleMenuOpen = (event, tag) => { + event.stopPropagation(); + setMenuAnchorEl(event.currentTarget); + setSelectedTagForMenu(tag); + }; + + // 处理菜单关闭 + const handleMenuClose = () => { + setMenuAnchorEl(null); + setSelectedTagForMenu(null); + }; + + // 打开编辑标签对话框 + const openEditDialog = () => { + setTagToEdit(selectedTagForMenu); + setEditDialogOpen(true); + handleMenuClose(); + }; + + // 关闭编辑标签对话框 + const closeEditDialog = () => { + setEditDialogOpen(false); + setTagToEdit(null); + }; + + // 处理编辑标签成功 + const handleEditTagSuccess = updatedTag => { + // 更新标签数据,不刷新页面 + const updateTagInTree = tagList => { + return tagList.map(tag => { + if (tag.id === updatedTag.id) { + return { ...tag, label: updatedTag.label }; + } + if (tag.children && tag.children.length > 0) { + return { ...tag, children: updateTagInTree(tag.children) }; + } + return tag; + }); + }; + + // 调用父组件的回调更新标签列表 + const updatedTags = updateTagInTree(tags); + onTagsUpdate?.(updatedTags); + }; + + // 打开删除确认对话框 + const openDeleteConfirm = () => { + console.log('打开删除确认对话框', selectedTagForMenu); + // 保存要删除的标签 + setTagToDelete(selectedTagForMenu); + setDeleteConfirmOpen(true); + handleMenuClose(); + }; + + // 关闭删除确认对话框 + const closeDeleteConfirm = () => { + setDeleteConfirmOpen(false); + }; + + // 处理删除标签 + const handleDeleteTag = () => { + if (!tagToDelete) { + console.log('没有要删除的标签信息'); + return; + } + + console.log('开始删除标签:', tagToDelete.id, tagToDelete.label); + + // 先关闭确认对话框 + closeDeleteConfirm(); + + // 执行删除操作 + const deleteTagAction = async () => { + try { + console.log('发送删除请求:', `/api/projects/${projectId}/tags?id=${tagToDelete.id}`); + + // 发送删除请求 + const response = await axios.delete(`/api/projects/${projectId}/tags?id=${tagToDelete.id}`); + + console.log('删除标签成功:', response.data); + + // 刷新页面 + window.location.reload(); + } catch (error) { + console.error('删除标签失败:', error); + console.error('错误详情:', error.response ? error.response.data : '无响应数据'); + alert(`删除标签失败: ${error.message}`); + } + }; + + // 立即执行删除操作 + deleteTagAction(); + }; + + // 打开删除问题确认对话框 + const openDeleteQuestionConfirm = (questionId, event) => { + event.stopPropagation(); + setQuestionToDelete(questionId); + setDeleteQuestionConfirmOpen(true); + }; + + // 关闭删除问题确认对话框 + const closeDeleteQuestionConfirm = () => { + setDeleteQuestionConfirmOpen(false); + setQuestionToDelete(null); + }; + + // 处理删除问题 + const handleDeleteQuestion = async () => { + if (!questionToDelete) return; + + try { + await axios.delete(`/api/projects/${projectId}/questions/${questionToDelete}`); + // 更新问题列表 + setTagQuestions(prev => { + const newQuestions = { ...prev }; + Object.keys(newQuestions).forEach(tagId => { + newQuestions[tagId] = newQuestions[tagId].filter(q => q.id !== questionToDelete); + }); + return newQuestions; + }); + // 关闭确认对话框 + closeDeleteQuestionConfirm(); + } catch (error) { + console.error('删除问题失败:', error); + } + }; + + // 处理生成数据集 + const handleGenerateDataset = async (questionId, questionInfo, event) => { + event.stopPropagation(); + // 设置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [questionId]: true + })); + await generateSingleDataset({ projectId, questionId, questionInfo }); + // 重置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [questionId]: false + })); + }; + + // 处理生成多轮对话数据集 + const handleGenerateMultiTurnDataset = async (questionId, questionInfo, event) => { + event.stopPropagation(); + + try { + // 设置处理状态 + setProcessingMultiTurnQuestions(prev => ({ + ...prev, + [questionId]: true + })); + + // 首先检查项目是否配置了多轮对话设置 + const configResponse = await axios.get(`/api/projects/${projectId}/tasks`); + if (configResponse.status !== 200) { + throw new Error('获取项目配置失败'); + } + + const config = configResponse.data; + const multiTurnConfig = { + systemPrompt: config.multiTurnSystemPrompt, + scenario: config.multiTurnScenario, + rounds: config.multiTurnRounds, + roleA: config.multiTurnRoleA, + roleB: config.multiTurnRoleB + }; + + // 检查是否已配置必要的多轮对话设置 + if ( + !multiTurnConfig.scenario || + !multiTurnConfig.roleA || + !multiTurnConfig.roleB || + !multiTurnConfig.rounds || + multiTurnConfig.rounds < 1 + ) { + throw new Error('请先在项目设置中配置多轮对话相关参数'); + } + + // 检查是否选择了模型 + if (!selectedModel || Object.keys(selectedModel).length === 0) { + throw new Error('请先选择一个模型'); + } + + // 调用多轮对话生成API + const response = await axios.post(`/api/projects/${projectId}/dataset-conversations`, { + questionId, + ...multiTurnConfig, + model: selectedModel, + language: 'zh-CN' + }); + + if (response.status === 200) { + // 成功后刷新问题统计 + fetchQuestionsStats(); + toast.success(t('datasets.multiTurnGenerateSuccess', { defaultValue: '多轮对话数据集生成成功!' })); + + // 通知父组件刷新统计信息 + if (typeof window !== 'undefined') { + window.dispatchEvent(new CustomEvent('refreshDistillStats')); + } + } + } catch (error) { + console.error('生成多轮对话数据集失败:', error); + toast.error(error.message || t('datasets.multiTurnGenerateError', { defaultValue: '生成多轮对话数据集失败' })); + } finally { + // 重置处理状态 + setProcessingMultiTurnQuestions(prev => ({ + ...prev, + [questionId]: false + })); + } + }; + + // 获取标签路径 + const getTagPath = useCallback( + tag => { + if (!tag) return ''; + + const findPath = (currentTag, path = []) => { + const newPath = [currentTag.label, ...path]; + + if (!currentTag.parentId) { + // 如果是顶级标签,确保路径以项目名称开始 + if (projectName && !newPath.includes(projectName)) { + return [projectName, ...newPath]; + } + return newPath; + } + + const parentTag = tags.find(t => t.id === currentTag.parentId); + if (!parentTag) { + // 如果没有找到父标签,确保路径以项目名称开始 + if (projectName && !newPath.includes(projectName)) { + return [projectName, ...newPath]; + } + return newPath; + } + + return findPath(parentTag, newPath); + }; + + const path = findPath(tag); + + // 最终检查,确保路径以项目名称开始 + if (projectName && path.length > 0 && path[0] !== projectName) { + path.unshift(projectName); + } + + return path.join(' > '); + }, + [tags, projectName] + ); + + // 渲染标签树 + const renderTagTree = (tagList, level = 0) => { + // 对同级标签进行排序 + const sortedTagList = sortTagsByNumber(tagList); + + return ( + + {sortedTagList.map(tag => ( + { + // 包装函数,处理问题生成后的刷新 + const handleGenerateQuestionsWithRefresh = async () => { + // 调用父组件传入的函数生成问题 + await onGenerateQuestions(tag, getTagPath(tag)); + + // 生成问题后刷新数据 + await fetchQuestionsStats(); + + // 如果标签已展开,刷新该标签的问题详情 + if (expandedTags[tag.id]) { + await fetchQuestionsByTag(tag.id); + } + }; + + handleGenerateQuestionsWithRefresh(); + }} + onGenerateSubTags={tag => onGenerateSubTags(tag, getTagPath(tag))} + questions={tagQuestions[tag.id] || []} + loadingQuestions={loadingQuestions[tag.id]} + processingQuestions={processingQuestions} + processingMultiTurnQuestions={processingMultiTurnQuestions} + onDeleteQuestion={openDeleteQuestionConfirm} + onGenerateDataset={handleGenerateDataset} + onGenerateMultiTurnDataset={handleGenerateMultiTurnDataset} + allQuestions={allQuestions} + tagQuestions={tagQuestions} + > + {/* 递归渲染子标签 */} + {tag.children && tag.children.length > 0 && expandedTags[tag.id] && renderTagTree(tag.children, level + 1)} + + ))} + + ); + }; + + return ( + + {tagTree.length > 0 ? ( + renderTagTree(tagTree) + ) : ( + + + {t('distill.noTags')} + + + )} + + {/* 标签操作菜单 */} + + + {/* 编辑标签对话框 */} + + + {/* 删除标签确认对话框 */} + + + {/* 删除问题确认对话框 */} + + + ); +}); + +export default DistillTreeView; diff --git a/easy-dataset-main/components/distill/QuestionGenerationDialog.js b/easy-dataset-main/components/distill/QuestionGenerationDialog.js new file mode 100644 index 0000000..2c9f19c --- /dev/null +++ b/easy-dataset-main/components/distill/QuestionGenerationDialog.js @@ -0,0 +1,194 @@ +'use client'; + +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Typography, + Box, + CircularProgress, + Alert, + List, + ListItem, + ListItemText, + Paper, + IconButton, + Divider +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import axios from 'axios'; +import i18n from '@/lib/i18n'; + +/** + * 问题生成对话框组件 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调函数 + * @param {Function} props.onGenerated - 问题生成完成的回调函数 + * @param {string} props.projectId - 项目ID + * @param {Object} props.tag - 标签对象 + * @param {string} props.tagPath - 标签路径 + * @param {Object} props.model - 选择的模型配置 + */ +export default function QuestionGenerationDialog({ open, onClose, onGenerated, projectId, tag, tagPath, model }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [count, setCount] = useState(5); + const [generatedQuestions, setGeneratedQuestions] = useState([]); + + // 处理生成问题 + const handleGenerateQuestions = async () => { + try { + setLoading(true); + setError(''); + + const response = await axios.post(`/api/projects/${projectId}/distill/questions`, { + tagPath, + currentTag: tag.label, + tagId: tag.id, + count, + model, + language: i18n.language + }); + + setGeneratedQuestions(response.data); + } catch (error) { + console.error('生成问题失败:', error); + setError(error.response?.data?.error || t('distill.generateQuestionsError')); + } finally { + setLoading(false); + } + }; + + // 处理生成完成 + const handleGenerateComplete = async () => { + if (onGenerated) { + onGenerated(generatedQuestions); + } + handleClose(); + }; + + // 处理关闭对话框 + const handleClose = () => { + setGeneratedQuestions([]); + setError(''); + setCount(5); + if (onClose) { + onClose(); + } + }; + + // 处理数量变化 + const handleCountChange = event => { + const value = parseInt(event.target.value); + if (!isNaN(value) && value >= 1 && value <= 100) { + setCount(value); + } + }; + + return ( + + + + {t('distill.generateQuestionsTitle', { tag: tag?.label || t('distill.unknownTag') })} + + + + + + + + {error && ( + + {error} + + )} + + + + {t('distill.tagPath')}: + + + {tagPath || tag?.label || t('distill.unknownTag')} + + + + + + {t('distill.questionCount')}: + + + + + {generatedQuestions.length > 0 && ( + + + {t('distill.generatedQuestions')}: + + + + {generatedQuestions.map((question, index) => ( + + {index > 0 && } + + + + + ))} + + + + )} + + + + + {generatedQuestions.length > 0 ? ( + + ) : ( + + )} + + + ); +} diff --git a/easy-dataset-main/components/distill/QuestionListItem.js b/easy-dataset-main/components/distill/QuestionListItem.js new file mode 100644 index 0000000..607a78b --- /dev/null +++ b/easy-dataset-main/components/distill/QuestionListItem.js @@ -0,0 +1,121 @@ +'use client'; + +import { useState } from 'react'; +import { + ListItem, + ListItemIcon, + ListItemText, + Box, + Typography, + Chip, + IconButton, + Tooltip, + CircularProgress +} from '@mui/material'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import ChatIcon from '@mui/icons-material/Chat'; +import { useTranslation } from 'react-i18next'; + +/** + * 问题列表项组件 + * @param {Object} props + * @param {Object} props.question - 问题对象 + * @param {number} props.level - 缩进级别 + * @param {Function} props.onDelete - 删除问题的回调 + * @param {Function} props.onGenerateDataset - 生成数据集的回调 + * @param {Function} props.onGenerateMultiTurnDataset - 生成多轮对话数据集的回调 + * @param {boolean} props.processing - 是否正在处理 + * @param {boolean} props.processingMultiTurn - 是否正在生成多轮对话 + */ +export default function QuestionListItem({ + question, + level, + onDelete, + onGenerateDataset, + onGenerateMultiTurnDataset, + processing = false, + processingMultiTurn = false +}) { + const { t } = useTranslation(); + + return ( + + + onGenerateDataset(e)} + disabled={processing || processingMultiTurn} + > + {processing ? : } + + + + onGenerateMultiTurnDataset && onGenerateMultiTurnDataset(e)} + disabled={processing || processingMultiTurn || !onGenerateMultiTurnDataset} + > + {processingMultiTurn ? : } + + + + onDelete(e)} + disabled={processing || processingMultiTurn} + > + + + + + } + > + + + + + + {question.question} + + {question.answered && ( + + )} + + } + /> + + ); +} diff --git a/easy-dataset-main/components/distill/TagEditDialog.js b/easy-dataset-main/components/distill/TagEditDialog.js new file mode 100644 index 0000000..800f961 --- /dev/null +++ b/easy-dataset-main/components/distill/TagEditDialog.js @@ -0,0 +1,115 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + Button, + CircularProgress, + Alert +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; +import { toast } from 'sonner'; + +/** + * 标签编辑对话框组件 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Object} props.tag - 要编辑的标签对象 + * @param {string} props.projectId - 项目ID + * @param {Function} props.onClose - 关闭对话框的回调 + * @param {Function} props.onSuccess - 编辑成功的回调 + */ +export default function TagEditDialog({ open, tag, projectId, onClose, onSuccess }) { + const { t } = useTranslation(); + const [newLabel, setNewLabel] = useState(''); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + + useEffect(() => { + if (open && tag) { + setNewLabel(tag.label); + setError(''); + } + }, [open, tag]); + + const handleConfirm = async () => { + if (!newLabel.trim()) { + setError(t('distill.labelRequired')); + return; + } + + if (newLabel === tag.label) { + onClose(); + return; + } + + try { + setLoading(true); + setError(''); + + const response = await axios.put(`/api/projects/${projectId}/distill/tags/${tag.id}`, { label: newLabel.trim() }); + + if (response.status === 200) { + toast.success(t('distill.tagUpdateSuccess')); + onSuccess?.(response.data); + onClose(); + } + } catch (err) { + console.error('更新标签失败:', err); + setError(err.response?.data?.error || t('distill.tagUpdateFailed')); + toast.error(err.response?.data?.error || t('distill.tagUpdateFailed')); + } finally { + setLoading(false); + } + }; + + const handleClose = () => { + if (!loading) { + onClose(); + } + }; + + return ( + + {t('distill.editTagTitle')} + + {error && ( + + {error} + + )} + setNewLabel(e.target.value)} + disabled={loading} + autoFocus + onKeyPress={e => { + if (e.key === 'Enter' && !loading) { + handleConfirm(); + } + }} + /> + + + + + + + ); +} diff --git a/easy-dataset-main/components/distill/TagGenerationDialog.js b/easy-dataset-main/components/distill/TagGenerationDialog.js new file mode 100644 index 0000000..386efc6 --- /dev/null +++ b/easy-dataset-main/components/distill/TagGenerationDialog.js @@ -0,0 +1,230 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Typography, + Box, + CircularProgress, + Alert, + Chip, + Paper, + IconButton +} from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; +import axios from 'axios'; +import i18n from '@/lib/i18n'; + +/** + * 标签生成对话框组件 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调函数 + * @param {Function} props.onGenerated - 标签生成完成的回调函数 + * @param {string} props.projectId - 项目ID + * @param {Object} props.parentTag - 父标签对象,为null时表示生成根标签 + * @param {string} props.tagPath - 标签链路 + * @param {Object} props.model - 选择的模型配置 + */ +export default function TagGenerationDialog({ open, onClose, onGenerated, projectId, parentTag, tagPath, model }) { + const { t } = useTranslation(); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [count, setCount] = useState(5); + const [generatedTags, setGeneratedTags] = useState([]); + const [parentTagName, setParentTagName] = useState(''); + const [project, setProject] = useState(null); + + // 获取项目信息,如果是顶级标签,默认填写项目名称 + useEffect(() => { + if (projectId && !parentTag) { + axios + .get(`/api/projects/${projectId}`) + .then(response => { + setProject(response.data); + setParentTagName(response.data.name || ''); + }) + .catch(error => { + console.error('获取项目信息失败:', error); + }); + } else if (parentTag) { + setParentTagName(parentTag.label || ''); + } + }, [projectId, parentTag]); + + // 处理生成标签 + const handleGenerateTags = async () => { + try { + setLoading(true); + setError(''); + + const response = await axios.post(`/api/projects/${projectId}/distill/tags`, { + parentTag: parentTagName, + parentTagId: parentTag ? parentTag.id : null, + tagPath: tagPath || parentTagName, + count, + model, + language: i18n.language + }); + + setGeneratedTags(response.data); + } catch (error) { + console.error('生成标签失败:', error); + setError(error.response?.data?.error || t('distill.generateTagsError')); + } finally { + setLoading(false); + } + }; + + // 处理生成完成 + const handleGenerateComplete = async () => { + if (onGenerated) { + onGenerated(generatedTags); + } + handleClose(); + }; + + // 处理关闭对话框 + const handleClose = () => { + setGeneratedTags([]); + setError(''); + setCount(5); + if (onClose) { + onClose(); + } + }; + + // 处理数量变化 + const handleCountChange = event => { + const value = parseInt(event.target.value); + if (!isNaN(value) && value >= 1 && value <= 100) { + setCount(value); + } + }; + + return ( + + + + {parentTag + ? t('distill.generateSubTagsTitle', { parentTag: parentTag.label }) + : t('distill.generateRootTagsTitle')} + + + + + + + + {error && ( + + {error} + + )} + + {/* 标签路径显示 */} + {parentTag && tagPath && ( + + + {t('distill.tagPath')}: + + + {tagPath || parentTag.label} + + + )} + + + + {t('distill.parentTag')}: + + + setParentTagName(e.target.value)} + placeholder={t('distill.parentTagPlaceholder')} + disabled={loading || !parentTag} + // 如果是顶级标签,设置为只读 + InputProps={{ + readOnly: !parentTag + }} + // 显示适当的帮助文本 + helperText={ + !parentTag + ? t('distill.rootTopicHelperText', { defaultValue: '使用项目名称作为顶级主题' }) + : t('distill.parentTagHelp') + } + /> + + + + + {t('distill.tagCount')}: + + + + + {generatedTags.length > 0 && ( + + + {t('distill.generatedTags')}: + + + + {generatedTags.map((tag, index) => ( + + ))} + + + + )} + + + + + {generatedTags.length > 0 ? ( + + ) : ( + + )} + + + ); +} diff --git a/easy-dataset-main/components/distill/TagMenu.js b/easy-dataset-main/components/distill/TagMenu.js new file mode 100644 index 0000000..f5f31a3 --- /dev/null +++ b/easy-dataset-main/components/distill/TagMenu.js @@ -0,0 +1,46 @@ +'use client'; + +import { Menu, MenuItem, ListItemIcon, ListItemText } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import EditIcon from '@mui/icons-material/Edit'; +import { useTranslation } from 'react-i18next'; + +/** + * 标签操作菜单组件 + * @param {Object} props + * @param {HTMLElement} props.anchorEl - 菜单锚点元素 + * @param {boolean} props.open - 菜单是否打开 + * @param {Function} props.onClose - 关闭菜单的回调 + * @param {Function} props.onEdit - 编辑操作的回调 + * @param {Function} props.onDelete - 删除操作的回调 + */ +export default function TagMenu({ anchorEl, open, onClose, onEdit, onDelete }) { + const { t } = useTranslation(); + + const handleEdit = () => { + onEdit?.(); + onClose(); + }; + + const handleDelete = () => { + onDelete?.(); + onClose(); + }; + + return ( + + + + + + {t('common.edit')} + + + + + + {t('common.delete')} + + + ); +} diff --git a/easy-dataset-main/components/distill/TagTreeItem.js b/easy-dataset-main/components/distill/TagTreeItem.js new file mode 100644 index 0000000..b2ebb9c --- /dev/null +++ b/easy-dataset-main/components/distill/TagTreeItem.js @@ -0,0 +1,240 @@ +'use client'; + +import { useState } from 'react'; +import { + Box, + Typography, + ListItem, + ListItemButton, + ListItemIcon, + ListItemText, + IconButton, + Collapse, + Chip, + Tooltip, + List, + CircularProgress +} from '@mui/material'; +import FolderIcon from '@mui/icons-material/Folder'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import AddIcon from '@mui/icons-material/Add'; +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import { useTranslation } from 'react-i18next'; +import QuestionListItem from './QuestionListItem'; + +/** + * 标签树项组件 + * @param {Object} props + * @param {Object} props.tag - 标签对象 + * @param {number} props.level - 缩进级别 + * @param {boolean} props.expanded - 是否展开 + * @param {Function} props.onToggle - 切换展开/折叠的回调 + * @param {Function} props.onMenuOpen - 打开菜单的回调 + * @param {Function} props.onGenerateQuestions - 生成问题的回调 + * @param {Function} props.onGenerateSubTags - 生成子标签的回调 + * @param {Array} props.questions - 标签下的问题列表 + * @param {boolean} props.loadingQuestions - 是否正在加载问题 + * @param {Object} props.processingQuestions - 正在处理的问题ID映射 + * @param {Function} props.onDeleteQuestion - 删除问题的回调 + * @param {Function} props.onGenerateDataset - 生成数据集的回调 + * @param {Function} props.onGenerateMultiTurnDataset - 生成多轮对话数据集的回调 + * @param {Object} props.processingMultiTurnQuestions - 正在生成多轮对话的问题ID映射 + * @param {Array} props.allQuestions - 所有问题列表(用于计算问题数量) + * @param {Object} props.tagQuestions - 标签问题映射 + * @param {React.ReactNode} props.children - 子标签内容 + */ +export default function TagTreeItem({ + tag, + level = 0, + expanded = false, + onToggle, + onMenuOpen, + onGenerateQuestions, + onGenerateSubTags, + questions = [], + loadingQuestions = false, + processingQuestions = {}, + onDeleteQuestion, + onGenerateDataset, + onGenerateMultiTurnDataset, + processingMultiTurnQuestions = {}, + allQuestions = [], + tagQuestions = {}, + children +}) { + const { t } = useTranslation(); + + // 递归计算所有层级的子标签数量 + const getTotalSubTagsCount = childrenTags => { + let count = childrenTags.length; + childrenTags.forEach(childTag => { + if (childTag.children && childTag.children.length > 0) { + count += getTotalSubTagsCount(childTag.children); + } + }); + return count; + }; + + // 递归获取所有子标签的问题数量 + const getChildrenQuestionsCount = childrenTags => { + let count = 0; + childrenTags.forEach(childTag => { + // 子标签的问题 + if (tagQuestions[childTag.id] && tagQuestions[childTag.id].length > 0) { + count += tagQuestions[childTag.id].length; + } else { + count += allQuestions.filter(q => q.label === childTag.label).length; + } + + // 子标签的子标签的问题 + if (childTag.children && childTag.children.length > 0) { + count += getChildrenQuestionsCount(childTag.children); + } + }); + return count; + }; + + // 计算当前标签的问题数量 + const getCurrentTagQuestionsCount = () => { + let currentTagQuestions = 0; + if (tagQuestions[tag.id] && tagQuestions[tag.id].length > 0) { + currentTagQuestions = tagQuestions[tag.id].length; + } else { + currentTagQuestions = allQuestions.filter(q => q.label === tag.label).length; + } + return currentTagQuestions; + }; + + // 总问题数量 = 当前标签的问题 + 所有子标签的问题 + const totalQuestions = + getCurrentTagQuestionsCount() + (tag.children ? getChildrenQuestionsCount(tag.children || []) : 0); + + return ( + + 0 ? '1px dashed rgba(0, 0, 0, 0.1)' : 'none', + ml: level > 0 ? 2 : 0 + }} + > + onToggle(tag.id)} sx={{ borderRadius: 1, py: 0.5 }}> + + + + + {tag.label} + {tag.children && tag.children.length > 0 && ( + + )} + {totalQuestions > 0 && ( + + )} + + } + primaryTypographyProps={{ component: 'div' }} + /> + + + + { + e.stopPropagation(); + onGenerateQuestions(tag); + }} + > + + + + + + { + e.stopPropagation(); + onGenerateSubTags(tag); + }} + > + + + + + onMenuOpen(e, tag)}> + + + + {tag.children && tag.children.length > 0 ? ( + expanded ? ( + + ) : ( + + ) + ) : null} + + + + + {/* 子标签 */} + {tag.children && tag.children.length > 0 && ( + + {children} + + )} + + {/* 标签下的问题 */} + {expanded && ( + + + {loadingQuestions ? ( + + + + {t('common.loading')} + + + ) : questions && questions.length > 0 ? ( + questions.map(question => ( + onDeleteQuestion(question.id, e)} + onGenerateDataset={e => onGenerateDataset(question.id, question.question, e)} + onGenerateMultiTurnDataset={ + onGenerateMultiTurnDataset ? e => onGenerateMultiTurnDataset(question.id, question, e) : undefined + } + /> + )) + ) : ( + + + {t('distill.noQuestions')} + + + )} + + + )} + + ); +} diff --git a/easy-dataset-main/components/distill/utils.js b/easy-dataset-main/components/distill/utils.js new file mode 100644 index 0000000..1665206 --- /dev/null +++ b/easy-dataset-main/components/distill/utils.js @@ -0,0 +1,72 @@ +'use client'; + +/** + * 按照标签前面的序号对标签进行排序 + * @param {Array} tags - 标签数组 + * @returns {Array} 排序后的标签数组 + */ +export const sortTagsByNumber = tags => { + return [...tags].sort((a, b) => { + // 提取标签前面的序号 + const getNumberPrefix = label => { + // 匹配形如 1, 1.1, 1.1.2 的序号 + const match = label.match(/^([\d.]+)\s/); + if (match) { + return match[1]; // 返回完整的序号字符串,如 "1.10" + } + return null; // 没有序号 + }; + + const aPrefix = getNumberPrefix(a.label); + const bPrefix = getNumberPrefix(b.label); + + // 如果两个标签都有序号,按序号比较 + if (aPrefix && bPrefix) { + // 将序号分解为数组,然后按数值比较 + const aParts = aPrefix.split('.').map(num => parseInt(num, 10)); + const bParts = bPrefix.split('.').map(num => parseInt(num, 10)); + + // 比较序号数组 + for (let i = 0; i < Math.min(aParts.length, bParts.length); i++) { + if (aParts[i] !== bParts[i]) { + return aParts[i] - bParts[i]; // 数值比较,确保 1.2 排在 1.10 前面 + } + } + // 如果前面的数字都相同,则较短的序号在前 + return aParts.length - bParts.length; + } + // 如果只有一个标签有序号,则有序号的在前 + else if (aPrefix) { + return -1; + } else if (bPrefix) { + return 1; + } + // 如果都没有序号,则按原来的字母序排序 + else { + return a.label.localeCompare(b.label, 'zh-CN'); + } + }); +}; + +/** + * 获取标签的完整路径 + * @param {Object} tag - 标签对象 + * @param {Array} allTags - 所有标签数组 + * @returns {string} 标签路径,如 "标签1 > 标签2 > 标签3" + */ +export const getTagPath = (tag, allTags) => { + if (!tag) return ''; + + const findPath = (currentTag, path = []) => { + const newPath = [currentTag.label, ...path]; + + if (!currentTag.parentId) return newPath; + + const parentTag = allTags.find(t => t.id === currentTag.parentId); + if (!parentTag) return newPath; + + return findPath(parentTag, newPath); + }; + + return findPath(tag).join(' > '); +}; diff --git a/easy-dataset-main/components/export/HuggingFaceTab.js b/easy-dataset-main/components/export/HuggingFaceTab.js new file mode 100644 index 0000000..7e677b5 --- /dev/null +++ b/easy-dataset-main/components/export/HuggingFaceTab.js @@ -0,0 +1,245 @@ +// HuggingFaceTab.js 组件 +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Typography, + Box, + TextField, + Button, + FormControlLabel, + Checkbox, + Alert, + CircularProgress, + Divider, + Paper, + Grid, + Tooltip, + IconButton, + Link +} from '@mui/material'; +import InfoIcon from '@mui/icons-material/Info'; +import HelpOutlineIcon from '@mui/icons-material/HelpOutline'; +import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; + +const HuggingFaceTab = ({ + projectId, + systemPrompt, + reasoningLanguage, + confirmedOnly, + includeCOT, + formatType, + fileFormat, + customFields, + handleSystemPromptChange, + handleReasoningLanguageChange, + handleConfirmedOnlyChange, + handleIncludeCOTChange +}) => { + const { t } = useTranslation(); + const [token, setToken] = useState(''); + const [datasetName, setDatasetName] = useState(''); + const [isPrivate, setIsPrivate] = useState(false); + const [uploading, setUploading] = useState(false); + const [error, setError] = useState(''); + const [success, setSuccess] = useState(false); + const [datasetUrl, setDatasetUrl] = useState(''); + const [hasToken, setHasToken] = useState(false); + const [loading, setLoading] = useState(true); + + // 从配置中获取 huggingfaceToken + useEffect(() => { + if (projectId) { + setLoading(true); + fetch(`/api/projects/${projectId}/config`) + .then(res => res.json()) + .then(data => { + if (data.huggingfaceToken) { + setToken(data.huggingfaceToken); + setHasToken(true); + } + setLoading(false); + }) + .catch(err => { + console.error('获取 HuggingFace Token 失败:', err); + setLoading(false); + }); + } + }, [projectId]); + + // 处理上传数据集到 HuggingFace + const handleUpload = async () => { + if (!hasToken) { + return; + } + + if (!datasetName) { + setError('请输入数据集名称'); + return; + } + + try { + setUploading(true); + setError(''); + setSuccess(false); + + const response = await fetch(`/api/projects/${projectId}/huggingface/upload`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + token, + datasetName, + isPrivate, + formatType, + systemPrompt, + reasoningLanguage, + confirmedOnly, + includeCOT, + fileFormat, + customFields: formatType === 'custom' ? customFields : undefined + }) + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error || '上传失败'); + } + + setSuccess(true); + setDatasetUrl(data.url); + } catch (err) { + setError(err.message); + } finally { + setUploading(false); + } + }; + + return ( + + {error && ( + + {error} + + )} + + {success && ( + }> + {t('export.uploadSuccess')} + {datasetUrl && ( + + + {t('export.viewOnHuggingFace')} + + + )} + + )} + + {!hasToken ? ( + + {t('export.noTokenWarning')} + + + + + ) : null} + + + + + + {t('export.datasetSettings')} + + + + + setDatasetName(e.target.value)} + helperText={t('export.datasetNameHelp')} + sx={{ mb: 2 }} + /> + + + + setIsPrivate(e.target.checked)} />} + label={t('export.privateDataset')} + /> + + + + + + + {t('export.exportOptions')} + + + + + {t('export.systemPrompt')} + + + + {/* Reasoning language – only for multilingual‑thinking */} + {formatType === 'multilingualthinking' && ( + + + {t('export.reasoningLanguage')} + + + + )} + + } + label={t('export.onlyConfirmed')} + /> + + } + label={t('export.includeCOT')} + /> + + + + + + + + ); +}; + +export default HuggingFaceTab; diff --git a/easy-dataset-main/components/export/LlamaFactoryTab.js b/easy-dataset-main/components/export/LlamaFactoryTab.js new file mode 100644 index 0000000..d7edb33 --- /dev/null +++ b/easy-dataset-main/components/export/LlamaFactoryTab.js @@ -0,0 +1,184 @@ +// LlamaFactoryTab.js 组件 +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Button, + FormControlLabel, + Checkbox, + Typography, + Box, + TextField, + Alert, + CircularProgress, + IconButton, + Tooltip +} from '@mui/material'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import CheckIcon from '@mui/icons-material/Check'; + +const LlamaFactoryTab = ({ + projectId, + systemPrompt, + reasoningLanguage, + confirmedOnly, + includeCOT, + formatType, + handleSystemPromptChange, + handleReasoningLanguageChange, + handleConfirmedOnlyChange, + handleIncludeCOTChange +}) => { + const { t } = useTranslation(); + const [configExists, setConfigExists] = useState(false); + const [configPath, setConfigPath] = useState(''); + const [generating, setGenerating] = useState(false); + const [error, setError] = useState(''); + const [copied, setCopied] = useState(false); + + // 检查配置文件是否存在 + useEffect(() => { + if (projectId) { + fetch(`/api/projects/${projectId}/llamaFactory/checkConfig`) + .then(res => res.json()) + .then(data => { + setConfigExists(data.exists); + if (data.exists) { + setConfigPath(data.configPath); + } + }) + .catch(err => { + setError(err.message); + }); + } + }, [projectId, configExists]); + + // 复制路径到剪贴板 + const handleCopyPath = () => { + const path = configPath.replace('dataset_info.json', ''); + navigator.clipboard.writeText(path).then(() => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }); + }; + + // 处理生成 Llama Factory 配置 + const handleGenerateConfig = async () => { + try { + setGenerating(true); + setError(''); + + const response = await fetch(`/api/projects/${projectId}/llamaFactory/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + formatType, + systemPrompt, + reasoningLanguage, + confirmedOnly, + includeCOT + }) + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.error); + } + + setConfigExists(true); + } catch (err) { + setError(err.message); + } finally { + setGenerating(false); + } + }; + + return ( + + {error && ( + + {error} + + )} + + + + {t('export.systemPrompt')} + + + + {/* Reasoning language – only for multilingual‑thinking */} + {formatType === 'multilingualthinking' && ( + + + {t('export.reasoningLanguage')} + + + + )} + + } + label={t('export.onlyConfirmed')} + /> + + } + label={t('export.includeCOT')} + /> + + + {configExists ? ( + <> + + {t('export.configExists')} + + + + {t('export.configPath')}: {configPath.replace('dataset_info.json', '')} + + + + {copied ? : } + + + + + ) : ( + + {t('export.noConfig')} + + )} + + + + + + ); +}; + +export default LlamaFactoryTab; diff --git a/easy-dataset-main/components/export/LocalExportTab.js b/easy-dataset-main/components/export/LocalExportTab.js new file mode 100644 index 0000000..0c303e7 --- /dev/null +++ b/easy-dataset-main/components/export/LocalExportTab.js @@ -0,0 +1,777 @@ +// LocalExportTab.js 组件 +import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Button, + FormControl, + FormControlLabel, + RadioGroup, + Radio, + TextField, + Checkbox, + Typography, + Box, + Paper, + useTheme, + Grid, + Table, + TableRow, + TableHead, + TableBody, + TableCell, + TableContainer, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Chip, + Alert, + CircularProgress +} from '@mui/material'; + +const LocalExportTab = ({ + fileFormat, + formatType, + systemPrompt, + confirmedOnly, + includeCOT, + customFields, + alpacaFieldType, + customInstruction, + reasoningLanguage, + handleFileFormatChange, + handleFormatChange, + handleSystemPromptChange, + handleReasoningLanguageChange, + handleConfirmedOnlyChange, + handleIncludeCOTChange, + handleCustomFieldChange, + handleIncludeLabelsChange, + handleIncludeChunkChange, + handleQuestionOnlyChange, + handleAlpacaFieldTypeChange, + handleCustomInstructionChange, + handleExport, + projectId +}) => { + const theme = useTheme(); + const { t } = useTranslation(); + + // Balance export related state + const [balanceDialogOpen, setBalanceDialogOpen] = useState(false); + const [tagStats, setTagStats] = useState([]); + const [balanceConfig, setBalanceConfig] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(''); + const [totalCount, setTotalCount] = useState(0); + + // Get label statistics (changed to GET + query parameters) + const fetchTagStats = async () => { + try { + setLoading(true); + const url = `/api/projects/${projectId}/datasets/export?confirmed=${confirmedOnly ? 'true' : 'false'}`; + const response = await fetch(url, { method: 'GET' }); + + if (!response.ok) { + throw new Error(t('errors.getTagStatsFailed')); + } + + const stats = await response.json(); + setTagStats(stats); + + // 初始化平衡配置 + const initialConfig = stats.map(stat => ({ + tagLabel: stat.tagLabel, + maxCount: Math.min(stat.datasetCount, 100), // 默认最多100条 + availableCount: stat.datasetCount + })); + + setBalanceConfig(initialConfig); + + // 计算总数 + const total = initialConfig.reduce((sum, config) => sum + config.maxCount, 0); + setTotalCount(total); + } catch (err) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + // 打开平衡导出对话框 + const handleOpenBalanceDialog = () => { + setBalanceDialogOpen(true); + fetchTagStats(); + }; + + // 更新单个标签的数量配置 + const updateBalanceConfig = (tagLabel, newCount) => { + const newConfig = balanceConfig.map(config => { + if (config.tagLabel === tagLabel) { + const count = Math.min(Math.max(0, parseInt(newCount) || 0), config.availableCount); + return { ...config, maxCount: count }; + } + return config; + }); + + setBalanceConfig(newConfig); + + // 重新计算总数 + const total = newConfig.reduce((sum, config) => sum + config.maxCount, 0); + setTotalCount(total); + }; + + // 一键设置所有标签为相同数量 + const setAllToSameCount = count => { + const newConfig = balanceConfig.map(config => ({ + ...config, + maxCount: Math.min(Math.max(0, parseInt(count) || 0), config.availableCount) + })); + + setBalanceConfig(newConfig); + + const total = newConfig.reduce((sum, config) => sum + config.maxCount, 0); + setTotalCount(total); + }; + + // 处理平衡导出 + const handleBalancedExport = () => { + // 过滤出数量大于0的配置 + const validConfig = balanceConfig.filter(config => config.maxCount > 0); + + if (validConfig.length === 0) { + setError(t('export.balancedExport.atLeastOneTag', '请至少为一个标签设置大于0的数量')); + return; + } + + // 调用原有的导出函数,但传递平衡配置 + handleExport({ + balanceMode: true, + balanceConfig: validConfig, + formatType, + systemPrompt, + reasoningLanguage, + confirmedOnly, + fileFormat, + includeCOT, + alpacaFieldType, + customInstruction, + customFields: formatType === 'custom' ? customFields : undefined + }); + + setBalanceDialogOpen(false); + }; + + // 自定义格式的示例 + const getCustomFormatExample = () => { + const { questionField, answerField, cotField, includeLabels, includeChunk } = customFields; + const example = { + [questionField]: t('sampleData.questionContent'), + [answerField]: t('sampleData.answerContent') + }; + + // 如果包含思维链字段,添加到示例中 + if (includeCOT) { + example[cotField] = t('sampleData.cotContent'); + } + + if (includeLabels) { + example.labels = [t('sampleData.domainLabel')]; + } + + if (includeChunk) { + example.chunk = t('sampleData.textChunk'); + } + + return fileFormat === 'json' ? JSON.stringify([example], null, 2) : JSON.stringify(example); + }; + + // CSV 自定义格式化示例 + const getPreviewData = () => { + if (formatType === 'alpaca') { + // 根据选择的字段类型生成不同的示例 + if (alpacaFieldType === 'instruction') { + return { + headers: ['instruction', 'input', 'output', 'system'], + rows: [ + { + instruction: t('export.sampleInstruction', '人类指令(必填)'), + input: '', + output: t('export.sampleOutput', '模型回答(必填)'), + system: t('export.sampleSystem', '系统提示词(选填)') + }, + { + instruction: t('export.sampleInstruction2', '第二个指令'), + input: '', + output: t('export.sampleOutput2', '第二个回答'), + system: t('export.sampleSystemShort', '系统提示词') + } + ] + }; + } else { + // input + return { + headers: ['instruction', 'input', 'output', 'system'], + rows: [ + { + instruction: customInstruction || t('export.fixedInstruction', '固定的指令内容'), + input: t('export.sampleInput', '人类问题(必填)'), + output: t('export.sampleOutput', '模型回答(必填)'), + system: t('export.sampleSystem', '系统提示词(选填)') + }, + { + instruction: customInstruction || t('export.fixedInstruction', '固定的指令内容'), + input: t('export.sampleInput2', '第二个问题'), + output: t('export.sampleOutput2', '第二个回答'), + system: t('export.sampleSystemShort', '系统提示词') + } + ] + }; + } + } else if (formatType === 'sharegpt') { + return { + headers: ['messages'], + rows: [ + { + messages: JSON.stringify( + [ + { + messages: [ + { + role: 'system', + content: t('export.sampleSystem', '系统提示词(选填)') + }, + { + role: 'user', + content: t('export.sampleUserMessage', '人类指令') // 映射到 question 字段 + }, + { + role: 'assistant', + content: t('export.sampleAssistantMessage', '模型回答') // 映射到 cot+answer 字段 + } + ] + } + ], + null, + 2 + ) + } + ] + }; + } else if (formatType === 'multilingualthinking') { + return { + headers: 'messages', + rows: { + messages: JSON.stringify( + { + reasoning_language: 'English', + developer: t('export.sampleSystem', '系统提示词(选填)'), + user: t('export.sampleUserMessage', '人类指令'), // 映射到 question 字段 + analysis: t('export.sampleAnalysis', '模型的思维链内容'), // 映射到 cot 字段 + final: t('export.sampleFinal', '模型回答'), // 映射到 answer 字段 + messages: [ + { + role: 'system', + content: '系统提示词(选填)', + thinking: 'null' + }, + { + role: 'user', + content: '人类指令', // 映射到 question 字段 + thinking: 'null' + }, + { + role: 'assistant', + content: '模型回答', // 映射到 answer 字段 + thinking: '模型的思维链内容' // 映射到 cot 字段 + } + ] + }, + null, + 2 + ) + } + }; + } else if (formatType === 'custom') { + // 如果选择仅导出问题,只包含问题字段 + if (customFields.questionOnly) { + const headers = [customFields.questionField]; + if (customFields.includeLabels) headers.push('labels'); + if (customFields.includeChunk) headers.push('chunk'); + + const row = { + [customFields.questionField]: t('sampleData.questionContent') + }; + if (customFields.includeLabels) row.labels = t('sampleData.domainLabel'); + if (customFields.includeChunk) row.chunk = t('sampleData.textChunk'); + return { + headers, + rows: [row] + }; + } else { + // 正常的自定义格式 + const headers = [customFields.questionField, customFields.answerField]; + if (includeCOT) headers.push(customFields.cotField); + if (customFields.includeLabels) headers.push('labels'); + if (customFields.includeChunk) headers.push('chunk'); + + const row = { + [customFields.questionField]: t('sampleData.questionContent'), + [customFields.answerField]: t('sampleData.answerContent') + }; + if (includeCOT) row[customFields.cotField] = t('sampleData.cotContent'); + if (customFields.includeLabels) row.labels = t('sampleData.domainLabel'); + if (customFields.includeChunk) row.chunk = t('sampleData.textChunk'); + return { + headers, + rows: [row] + }; + } + } + }; + + return ( + <> + + + {t('export.fileFormat')} + + + + } label="JSON" /> + } label="JSONL" /> + {/* } label="CSV" /> */} + } + label="CSV" + /> + + + + + {/* 数据集风格 */} + + + {t('export.format')} + + + + } label="Alpaca" /> + } label="ShareGPT" /> + {/* NEW: Multilingual‑Thinking format */} + } + label={t('export.multilingualThinkingFormat') || 'Multilingual‑Thinking'} + /> + } label={t('export.customFormat')} /> + + + + + {/* Alpaca 格式特有的设置 */} + {formatType === 'alpaca' && ( + + + {t('export.alpacaSettings', 'Alpaca 格式设置')} + + + + {t('export.questionFieldType', '问题字段类型')} + + + } + label={t('export.useInstruction', '使用 instruction 字段')} + /> + } label={t('export.useInput', '使用 input 字段')} /> + + + {alpacaFieldType === 'input' && ( + + )} + + + )} + + {/* 自定义格式选项 */} + {formatType === 'custom' && ( + + + {t('export.customFormatSettings')} + + + + + + + + + {/* 添加思维链字段名输入框 */} + + + + + + } + label={t('export.includeLabels')} + /> + } + label={t('export.includeChunk')} + /> + } + label={t('export.questionOnly')} + /> + + )} + + + + {t('export.example')} + + + {fileFormat === 'csv' ? ( + + {(() => { + const { headers, rows } = getPreviewData(); + const tableKey = `${formatType}-${fileFormat}-${JSON.stringify(customFields)}`; + return ( + + + + {headers.map(header => ( + {header} + ))} + + + + {rows.map((row, index) => ( + + {headers.map(header => ( + + {Array.isArray(row[header]) ? row[header].join(', ') : row[header] || ''} + + ))} + + ))} + +
+ ); + })()} +
+ ) : ( + +
+              {formatType === 'custom'
+                ? getCustomFormatExample()
+                : formatType === 'multilingualthinking'
+                  ? fileFormat === 'json'
+                    ? JSON.stringify(
+                        {
+                          reasoning_language: 'English',
+                          developer: '系统提示词(选填)',
+                          user: '人类指令', // 映射到 question 字段
+                          analysis: '模型的思维链内容', // 映射到 cot 字段
+                          final: '模型回答', // 映射到 answer 字段
+                          messages: [
+                            {
+                              content: t('export.sampleSystem', '系统提示词(选填)'),
+                              role: 'system',
+                              thinking: null
+                            },
+                            {
+                              content: t('export.sampleUserMessage', '人类指令'),
+                              role: 'user',
+                              thinking: null
+                            },
+                            {
+                              content: t('export.sampleAssistantMessage', '模型回答'),
+                              role: 'assistant',
+                              thinking: t('export.sampleThinking', '模型的思维链内容')
+                            }
+                          ]
+                        },
+                        null,
+                        2
+                      )
+                    : '{"reasoning_language": "English","developer": "系统提示词(选填)", "user": "人类指令", "analysis": "模型的思维链内容", "final": "模型回答", "messages": [{"role": "user", "content": "人类指令", "thinking": "null"}, {"role": "assistant", "content": "模型回答", "thinking": "模型的思维链内容"}]}'
+                  : formatType === 'alpaca'
+                    ? fileFormat === 'json'
+                      ? JSON.stringify(
+                          [
+                            {
+                              instruction: t('export.sampleInstruction', '人类指令(必填)'), // 映射到 question 字段
+                              input: t('export.sampleInputOptional', '人类输入(选填)'),
+                              output: t('export.sampleOutput', '模型回答(必填)'), // 映射到 cot+answer 字段
+                              system: t('export.sampleSystem', '系统提示词(选填)')
+                            }
+                          ],
+                          null,
+                          2
+                        )
+                      : '{"instruction": "人类指令(必填)", "input": "人类输入(选填)", "output": "模型回答(必填)", "system": "系统提示词(选填)"}\n{"instruction": "第二个指令", "input": "", "output": "第二个回答", "system": "系统提示词"}'
+                    : fileFormat === 'json'
+                      ? JSON.stringify(
+                          [
+                            {
+                              messages: [
+                                {
+                                  role: 'system',
+                                  content: t('export.sampleSystem', '系统提示词(选填)')
+                                },
+                                {
+                                  role: 'user',
+                                  content: t('export.sampleUserMessage', '人类指令') // 映射到 question 字段
+                                },
+                                {
+                                  role: 'assistant',
+                                  content: t('export.sampleAssistantMessage', '模型回答') // 映射到 cot+answer 字段
+                                }
+                              ]
+                            }
+                          ],
+                          null,
+                          2
+                        )
+                      : '{"messages": [{"role": "system", "content": "系统提示词(选填)"}, {"role": "user", "content": "人类指令"}, {"role": "assistant", "content": "模型回答"}]}\n{"messages": [{"role": "user", "content": "第二个问题"}, {"role": "assistant", "content": "第二个回答"}]}'}
+            
+
+ )} +
+ + + + {t('export.systemPrompt')} + + + + {/* Reasoning language – only for multilingual‑thinking */} + {formatType === 'multilingualthinking' && ( + + + {t('export.Reasoninglanguage')} + + + + )} + + } + label={t('export.onlyConfirmed')} + /> + + } + label={t('export.includeCOT')} + /> + + + + + + + + {/* 平衡导出对话框 */} + setBalanceDialogOpen(false)} + maxWidth="md" + fullWidth + PaperProps={{ + sx: { + borderRadius: 2 + } + }} + > + {t('exportDialog.balancedExportTitle')} + + + {t('exportDialog.balancedExportDescription')} + + + {error && ( + + {error} + + )} + + {loading ? ( + + + + ) : ( + <> + {/* 批量设置 */} + + + {t('exportDialog.quickSettings')} + + + + + + { + if (e.key === 'Enter') { + setAllToSameCount(e.target.value); + e.target.value = ''; + } + }} + /> + + + + {/* 标签配置表格 */} + + + + + {t('exportDialog.tagName')} + {t('exportDialog.availableCount')} + {t('exportDialog.exportCount')} + {t('exportDialog.settings')} + + + + {balanceConfig.map(config => ( + + + + + {config.availableCount} + + {config.maxCount} + + + updateBalanceConfig(config.tagLabel, e.target.value)} + inputProps={{ + min: 0, + max: config.availableCount, + style: { textAlign: 'right' } + }} + sx={{ width: 80 }} + /> + + + ))} + +
+
+ + {/* 统计信息 */} + + + + {t('exportDialog.totalExportCount')}: {totalCount} + {' '} + | {t('exportDialog.tagCount')}: {balanceConfig.filter(c => c.maxCount > 0).length} /{' '} + {balanceConfig.length} + + + + )} +
+ + + + +
+ + ); +}; + +export default LocalExportTab; diff --git a/easy-dataset-main/components/home/CreateProjectDialog.js b/easy-dataset-main/components/home/CreateProjectDialog.js new file mode 100644 index 0000000..cca5d06 --- /dev/null +++ b/easy-dataset-main/components/home/CreateProjectDialog.js @@ -0,0 +1,173 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + Box, + Typography, + useTheme, + CircularProgress, + FormControl, + InputLabel, + Select, + MenuItem +} from '@mui/material'; +import { useRouter } from 'next/navigation'; +import { useTranslation } from 'react-i18next'; + +export default function CreateProjectDialog({ open, onClose }) { + const { t } = useTranslation(); + const theme = useTheme(); + const router = useRouter(); + const [loading, setLoading] = useState(false); + const [projects, setProjects] = useState([]); + const [formData, setFormData] = useState({ + name: '', + description: '', + reuseConfigFrom: '' + }); + const [error, setError] = useState(null); + + // 获取项目列表 + useEffect(() => { + const fetchProjects = async () => { + try { + const response = await fetch('/api/projects'); + if (response.ok) { + const data = await response.json(); + setProjects(data); + } + } catch (error) { + console.error('获取项目列表失败:', error); + } + }; + + fetchProjects(); + }, []); + + const handleChange = e => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleSubmit = async e => { + e.preventDefault(); + setLoading(true); + setError(null); + + try { + const response = await fetch('/api/projects', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + + if (!response.ok) { + throw new Error(t('projects.createFailed')); + } + + const data = await response.json(); + + router.push(`/projects/${data.id}/settings?tab=model`); + } catch (err) { + console.error(t('projects.createError'), err); + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( + + + + {t('projects.createNew')} + + +
+ + + + + + {t('projects.reuseConfig')} + + + + {error && ( + + {error} + + )} + + + + + +
+
+ ); +} diff --git a/easy-dataset-main/components/home/HeroSection.js b/easy-dataset-main/components/home/HeroSection.js new file mode 100644 index 0000000..74a50c0 --- /dev/null +++ b/easy-dataset-main/components/home/HeroSection.js @@ -0,0 +1,135 @@ +'use client'; + +import { Box, Container, Typography, Button, useMediaQuery } from '@mui/material'; +import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; +import SearchIcon from '@mui/icons-material/Search'; +import { styles } from '@/styles/home'; +import { useTheme } from '@mui/material'; +import { motion } from 'framer-motion'; +import ParticleBackground from './ParticleBackground'; +import { useTranslation } from 'react-i18next'; + +export default function HeroSection({ onCreateProject }) { + const { t } = useTranslation(); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + return ( + + {/* 添加粒子背景 */} + + + + + + + + + {t('home.title')} + + + + {t('home.subtitle')} + + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/home/MigrationDialog.js b/easy-dataset-main/components/home/MigrationDialog.js new file mode 100644 index 0000000..3a04b8e --- /dev/null +++ b/easy-dataset-main/components/home/MigrationDialog.js @@ -0,0 +1,300 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + CircularProgress, + List, + ListItem, + ListItemText, + ListItemSecondaryAction, + IconButton, + Alert, + Paper, + useTheme, + Tooltip +} from '@mui/material'; +import WarningAmberIcon from '@mui/icons-material/WarningAmber'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { useTranslation } from 'react-i18next'; + +/** + * 项目迁移对话框组件 + * @param {Object} props - 组件属性 + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调函数 + * @param {Array} props.projectIds - 需要迁移的项目ID列表 + */ +export default function MigrationDialog({ open, onClose, projectIds = [] }) { + const { t } = useTranslation(); + const theme = useTheme(); + const [migrating, setMigrating] = useState(false); + const [success, setSuccess] = useState(false); + const [error, setError] = useState(null); + const [migratedCount, setMigratedCount] = useState(0); + const [taskId, setTaskId] = useState(null); + const [progress, setProgress] = useState(0); + const [statusText, setStatusText] = useState(''); + const [processingIds, setProcessingIds] = useState([]); + + // 打开项目目录 + const handleOpenDirectory = async projectId => { + try { + setProcessingIds(prev => [...prev, projectId]); + + const response = await fetch('/api/projects/open-directory', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ projectId }) + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || t('migration.openDirectoryFailed')); + } + + // 成功打开目录,不需要特别处理 + } catch (err) { + console.error('打开目录错误:', err); + setError(err.message); + } finally { + setProcessingIds(prev => prev.filter(id => id !== projectId)); + } + }; + + // 删除项目目录 + const handleDeleteDirectory = async projectId => { + try { + if (!window.confirm(t('migration.confirmDelete'))) { + return; + } + + setProcessingIds(prev => [...prev, projectId]); + + const response = await fetch('/api/projects/delete-directory', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ projectId }) + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || t('migration.deleteDirectoryFailed')); + } + + // 从列表中移除已删除的项目 + const updatedProjectIds = projectIds.filter(id => id !== projectId); + // 这里我们不能直接修改 projectIds,因为它是从父组件传入的 + // 但我们可以通知用户界面刷新 + window.location.reload(); + } catch (err) { + console.error('删除目录错误:', err); + setError(err.message); + } finally { + setProcessingIds(prev => prev.filter(id => id !== projectId)); + } + }; + + // 处理迁移操作 + const handleMigration = async () => { + try { + setMigrating(true); + setError(null); + setSuccess(false); + setProgress(0); + setStatusText(t('migration.starting')); + + // 调用异步迁移接口启动迁移任务 + const response = await fetch('/api/projects/migrate', { + method: 'POST' + }); + + if (!response.ok) { + throw new Error(t('migration.failed')); + } + + const { success, taskId: newTaskId } = await response.json(); + + if (!success || !newTaskId) { + throw new Error(t('migration.startFailed')); + } + + // 保存任务ID + setTaskId(newTaskId); + setStatusText(t('migration.processing')); + + // 开始轮询任务状态 + await pollMigrationStatus(newTaskId); + } catch (err) { + console.error('迁移错误:', err); + setError(err.message); + setMigrating(false); + } + }; + + // 轮询迁移任务状态 + const pollMigrationStatus = async id => { + try { + // 定义轮询间隔(毫秒) + const pollInterval = 1000; + + // 发送请求获取任务状态 + const response = await fetch(`/api/projects/migrate?taskId=${id}`); + + if (!response.ok) { + throw new Error(t('migration.statusFailed')); + } + + const { success, task } = await response.json(); + + if (!success || !task) { + throw new Error(t('migration.taskNotFound')); + } + + // 更新进度 + setProgress(task.progress || 0); + + // 根据任务状态更新UI + if (task.status === 'completed') { + // 任务完成 + setMigratedCount(task.completed); + setSuccess(true); + setMigrating(false); + setStatusText(t('migration.completed')); + + // 迁移成功后,延迟关闭对话框并刷新页面 + setTimeout(() => { + onClose(); + window.location.reload(); + }, 2000); + } else if (task.status === 'failed') { + // 任务失败 + throw new Error(task.error || t('migration.failed')); + } else { + // 任务仍在进行中,继续轮询 + setTimeout(() => pollMigrationStatus(id), pollInterval); + + // 更新状态文本 + if (task.total > 0) { + setStatusText( + t('migration.progressStatus', { + completed: task.completed || 0, + total: task.total + }) + ); + } + } + } catch (err) { + console.error('获取迁移状态错误:', err); + setError(err.message); + setMigrating(false); + } + }; + + return ( + + + + {t('migration.title')} + + + + {success ? ( + + {t('migration.success', { count: migratedCount })} + + ) : error ? ( + + {error} + + ) : null} + + + {t('migration.description')} + + + {projectIds.length > 0 && ( + + + {t('migration.projectsList')}: + + + + {projectIds.map(id => ( + + + + + handleOpenDirectory(id)} + disabled={processingIds.includes(id)} + size="small" + > + + + + + handleDeleteDirectory(id)} + disabled={processingIds.includes(id)} + size="small" + sx={{ ml: 1, color: 'error.main' }} + > + + + + + + ))} + + + + )} + + {migrating && ( + + 0 ? 'determinate' : 'indeterminate'} value={progress} /> + + {statusText || t('migration.migrating')} + + {progress > 0 && ( + + {progress}% + + )} + + )} + + + + + + + + ); +} diff --git a/easy-dataset-main/components/home/ParticleBackground.js b/easy-dataset-main/components/home/ParticleBackground.js new file mode 100644 index 0000000..80b9569 --- /dev/null +++ b/easy-dataset-main/components/home/ParticleBackground.js @@ -0,0 +1,251 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import { useTheme } from '@mui/material'; + +export default function ParticleBackground() { + const canvasRef = useRef(null); + const theme = useTheme(); + + useEffect(() => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + let animationFrameId; + let particles = []; + let mousePosition = { x: 0, y: 0 }; + let hoverRadius = 150; // 增加鼠标影响范围 + let mouseSpeed = { x: 0, y: 0 }; // 跟踪鼠标速度 + let lastMousePosition = { x: 0, y: 0 }; // 上一帧鼠标位置 + + // 设置画布大小为窗口大小 + const handleResize = () => { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + initParticles(); + }; + + // 跟踪鼠标位置和速度 + const handleMouseMove = event => { + // 计算鼠标速度 + mouseSpeed.x = event.clientX - mousePosition.x; + mouseSpeed.y = event.clientY - mousePosition.y; + + // 更新鼠标位置 + lastMousePosition.x = mousePosition.x; + lastMousePosition.y = mousePosition.y; + mousePosition.x = event.clientX; + mousePosition.y = event.clientY; + }; + + // 触摸设备支持 + const handleTouchMove = event => { + if (event.touches.length > 0) { + // 计算触摸速度 + mouseSpeed.x = event.touches[0].clientX - mousePosition.x; + mouseSpeed.y = event.touches[0].clientY - mousePosition.y; + + // 更新触摸位置 + lastMousePosition.x = mousePosition.x; + lastMousePosition.y = mousePosition.y; + mousePosition.x = event.touches[0].clientX; + mousePosition.y = event.touches[0].clientY; + } + }; + + // 生成随机颜色 + const getRandomColor = () => { + // 主题色调 + const colors = + theme.palette.mode === 'dark' + ? [ + 'rgba(255, 255, 255, 0.5)', // 白色 + 'rgba(100, 181, 246, 0.5)', // 蓝色 + 'rgba(156, 39, 176, 0.4)', // 紫色 + 'rgba(121, 134, 203, 0.5)' // 靛蓝色 + ] + : [ + 'rgba(42, 92, 170, 0.5)', // 主蓝色 + 'rgba(66, 165, 245, 0.4)', // 浅蓝色 + 'rgba(94, 53, 177, 0.3)', // 深紫色 + 'rgba(3, 169, 244, 0.4)' // 天蓝色 + ]; + + return colors[Math.floor(Math.random() * colors.length)]; + }; + + // 初始化粒子 + const initParticles = () => { + particles = []; + // 增加粒子数量,但保持性能平衡 + const particleCount = Math.min(Math.floor(window.innerWidth / 8), 150); + + for (let i = 0; i < particleCount; i++) { + // 创建不同大小和速度的粒子 + const size = Math.random(); + const speedFactor = Math.max(0.1, size); // 较大的粒子移动较慢 + + particles.push({ + x: Math.random() * canvas.width, + y: Math.random() * canvas.height, + // 粒子大小更加多样化 + radius: size * 3 + 0.5, + // 使用随机颜色 + color: getRandomColor(), + // 添加发光效果 + glow: Math.random() * 10 + 5, + // 调整速度范围,使运动更加自然 + speedX: (Math.random() * 0.6 - 0.3) * speedFactor, + speedY: (Math.random() * 0.6 - 0.3) * speedFactor, + originalSpeedX: (Math.random() * 0.6 - 0.3) * speedFactor, + originalSpeedY: (Math.random() * 0.6 - 0.3) * speedFactor, + // 添加脉动效果 + pulseSpeed: Math.random() * 0.02 + 0.01, + pulseDirection: Math.random() > 0.5 ? 1 : -1, + pulseAmount: 0, + // 粒子透明度 + opacity: Math.random() * 0.5 + 0.5 + }); + } + }; + + // 绘制粒子 + const drawParticles = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height); + + // 计算鼠标速度衰减 + mouseSpeed.x *= 0.95; + mouseSpeed.y *= 0.95; + + // 绘制粒子之间的连线 + drawLines(); + + particles.forEach(particle => { + // 计算粒子与鼠标的距离 + const dx = mousePosition.x - particle.x; + const dy = mousePosition.y - particle.y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // 脉动效果 + particle.pulseAmount += particle.pulseSpeed * particle.pulseDirection; + if (Math.abs(particle.pulseAmount) > 0.5) { + particle.pulseDirection *= -1; + } + + // 如果粒子在鼠标影响范围内,调整其速度 + if (distance < hoverRadius) { + const angle = Math.atan2(dy, dx); + const force = (hoverRadius - distance) / hoverRadius; + const mouseFactor = 3; // 增强鼠标影响力度 + + // 粒子远离鼠标,并受鼠标速度影响 + particle.speedX = -Math.cos(angle) * force * mouseFactor + particle.originalSpeedX + mouseSpeed.x * 0.05; + particle.speedY = -Math.sin(angle) * force * mouseFactor + particle.originalSpeedY + mouseSpeed.y * 0.05; + } else { + // 逐渐恢复原始速度 + particle.speedX = particle.speedX * 0.95 + particle.originalSpeedX * 0.05; + particle.speedY = particle.speedY * 0.95 + particle.originalSpeedY * 0.05; + } + + // 更新粒子位置 + particle.x += particle.speedX; + particle.y += particle.speedY; + + // 边界检查 + if (particle.x < 0) particle.x = canvas.width; + if (particle.x > canvas.width) particle.x = 0; + if (particle.y < 0) particle.y = canvas.height; + if (particle.y > canvas.height) particle.y = 0; + + // 应用脉动效果到粒子大小 + const currentRadius = particle.radius * (1 + particle.pulseAmount * 0.2); + + // 绘制发光效果 + const gradient = ctx.createRadialGradient(particle.x, particle.y, 0, particle.x, particle.y, particle.glow); + gradient.addColorStop(0, particle.color); + gradient.addColorStop(1, 'rgba(0, 0, 0, 0)'); + + // 绘制粒子 + ctx.beginPath(); + ctx.arc(particle.x, particle.y, currentRadius, 0, Math.PI * 2); + ctx.fillStyle = particle.color; + ctx.fill(); + + // 添加发光效果 + ctx.beginPath(); + ctx.arc(particle.x, particle.y, particle.glow, 0, Math.PI * 2); + ctx.fillStyle = gradient; + ctx.globalAlpha = 0.3 * particle.opacity; + ctx.fill(); + ctx.globalAlpha = 1.0; + }); + + animationFrameId = requestAnimationFrame(drawParticles); + }; + + // 绘制粒子之间的连线 + const drawLines = () => { + for (let i = 0; i < particles.length; i++) { + for (let j = i + 1; j < particles.length; j++) { + const dx = particles[i].x - particles[j].x; + const dy = particles[i].y - particles[j].y; + const distance = Math.sqrt(dx * dx + dy * dy); + + // 增加连线的最大距离 + const maxDistance = 120; + + if (distance < maxDistance) { + // 只在粒子距离小于maxDistance时绘制连线 + ctx.beginPath(); + ctx.moveTo(particles[i].x, particles[i].y); + ctx.lineTo(particles[j].x, particles[j].y); + + // 根据距离设置线条透明度 + const opacity = 1 - distance / maxDistance; + + // 根据主题设置线条颜色 + const lineColor = + theme.palette.mode === 'dark' + ? `rgba(255, 255, 255, ${opacity * 0.2})` + : `rgba(42, 92, 170, ${opacity * 0.2})`; + + ctx.strokeStyle = lineColor; + ctx.lineWidth = opacity * 1.5; // 根据距离调整线宽 + ctx.stroke(); + } + } + } + }; + + // 初始化 + handleResize(); + window.addEventListener('resize', handleResize); + window.addEventListener('mousemove', handleMouseMove); + window.addEventListener('touchmove', handleTouchMove); + + // 开始动画 + drawParticles(); + + // 清理函数 + return () => { + window.removeEventListener('resize', handleResize); + window.removeEventListener('mousemove', handleMouseMove); + window.removeEventListener('touchmove', handleTouchMove); + cancelAnimationFrame(animationFrameId); + }; + }, [theme.palette.mode]); + + return ( + + ); +} diff --git a/easy-dataset-main/components/home/ProjectCard.js b/easy-dataset-main/components/home/ProjectCard.js new file mode 100644 index 0000000..e3f86ee --- /dev/null +++ b/easy-dataset-main/components/home/ProjectCard.js @@ -0,0 +1,252 @@ +'use client'; + +import { + Card, + Box, + CardActionArea, + CardContent, + Typography, + Avatar, + Divider, + IconButton, + Menu, + MenuItem, + ListItemIcon +} from '@mui/material'; +import Link from 'next/link'; +import { styles } from '@/styles/home'; +import { useTheme, alpha } from '@mui/material/styles'; +import DataObjectIcon from '@mui/icons-material/DataObject'; +import DeleteIcon from '@mui/icons-material/Delete'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import TokenIcon from '@mui/icons-material/Token'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import QuizIcon from '@mui/icons-material/Quiz'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; + +/** + * 统计项组件 + */ +const StatItem = ({ icon: Icon, count, label, color, isToken }) => { + const theme = useTheme(); + + // 格式化数字 + const displayCount = isToken ? (count || 0).toLocaleString() : count || 0; + + return ( + + + + + + + {displayCount} + + + {label} + + + + ); +}; + +/** + * 项目卡片组件 + * @param {Object} props - 组件属性 + * @param {Object} props.project - 项目数据 + * @param {Function} props.onDeleteClick - 删除按钮点击事件处理函数 + */ +export default function ProjectCard({ project, onDeleteClick }) { + const { t } = useTranslation(); + const theme = useTheme(); + const [processingId, setProcessingId] = useState(false); + + // 菜单状态 + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + // 打开项目目录 + const handleOpenDirectory = async event => { + event.stopPropagation(); + event.preventDefault(); + + if (processingId) return; + + try { + setProcessingId(true); + + const response = await fetch('/api/projects/open-directory', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ projectId: project.id }) + }); + + if (!response.ok) { + const data = await response.json(); + throw new Error(data.error || t('migration.openDirectoryFailed')); + } + + // 成功打开目录,不需要特别处理 + } catch (error) { + console.error('打开目录错误:', error); + alert(error.message); + } finally { + setProcessingId(false); + } + }; + + // 处理菜单打开 + const handleMenuClick = event => { + event.stopPropagation(); + event.preventDefault(); + setAnchorEl(event.currentTarget); + }; + + // 处理菜单关闭 + const handleMenuClose = event => { + if (event) { + event.stopPropagation(); + event.preventDefault(); + } + setAnchorEl(null); + }; + + // 处理打开目录点击 + const handleOpenDirectoryClick = event => { + handleMenuClose(event); + handleOpenDirectory(event); + }; + + // 处理删除点击 + const handleDeleteClick = event => { + handleMenuClose(event); + onDeleteClick(event, project); + }; + + return ( + + + + + {/* 头部:Avatar + Title + Menu */} + + + + {project.name.charAt(0).toUpperCase()} + + + + {project.name} + + + ID: {project.id} + + + + + + + + + {/* 描述 */} + + {project.description || t('projects.noDescription', { defaultValue: '暂无描述' })} + + + {/* 统计数据 */} + + + + + + + + + + + {/* 操作菜单 */} + { + e.preventDefault(); + e.stopPropagation(); + }} + transformOrigin={{ horizontal: 'right', vertical: 'top' }} + anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} + PaperProps={{ + elevation: 3, + sx: { + borderRadius: '12px', + minWidth: 160, + mt: 0.5 + } + }} + > + + + + + {t('projects.openDirectory')} + + + + + + + + + {t('common.delete')} + + + + ); +} diff --git a/easy-dataset-main/components/home/ProjectList.js b/easy-dataset-main/components/home/ProjectList.js new file mode 100644 index 0000000..aa37453 --- /dev/null +++ b/easy-dataset-main/components/home/ProjectList.js @@ -0,0 +1,117 @@ +'use client'; + +import { + Grid, + Paper, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Typography +} from '@mui/material'; +import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import ProjectCard from './ProjectCard'; + +export default function ProjectList({ projects, onCreateProject }) { + const { t } = useTranslation(); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [projectToDelete, setProjectToDelete] = useState(null); + const [loading, setLoading] = useState(false); + // 打开删除确认对话框 + const handleOpenDeleteDialog = (event, project) => { + setProjectToDelete(project); + setDeleteDialogOpen(true); + }; + + // 关闭删除确认对话框 + const handleCloseDeleteDialog = () => { + setDeleteDialogOpen(false); + setProjectToDelete(null); + }; + + // 删除项目 + const handleDeleteProject = async () => { + if (!projectToDelete) return; + + try { + setLoading(true); + const response = await fetch(`/api/projects/${projectToDelete.id}`, { + method: 'DELETE' + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('projects.deleteFailed')); + } + + // 刷新页面以更新项目列表 + window.location.reload(); + } catch (error) { + console.error('删除项目失败:', error); + alert(error.message || t('projects.deleteFailed')); + } finally { + setLoading(false); + handleCloseDeleteDialog(); + } + }; + + return ( + <> + + {projects.length === 0 ? ( + + + + {t('projects.noProjects')} + + + + + ) : ( + projects.map(project => ( + + + + )) + )} + + + {/* 删除确认对话框 */} + + {t('projects.deleteConfirmTitle')} + + + {projectToDelete && ( + <> + {t('projects.deleteConfirm')} +
+ + {projectToDelete.name} + + + )} +
+
+ + + + +
+ + ); +} diff --git a/easy-dataset-main/components/home/StatsCard.js b/easy-dataset-main/components/home/StatsCard.js new file mode 100644 index 0000000..ab28f34 --- /dev/null +++ b/easy-dataset-main/components/home/StatsCard.js @@ -0,0 +1,118 @@ +'use client'; + +import { Paper, Grid, Box, Typography, useMediaQuery, Avatar } from '@mui/material'; +import { styles } from '@/styles/home'; +import { useTheme } from '@mui/material'; +import { motion } from 'framer-motion'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import QuestionAnswerIcon from '@mui/icons-material/QuestionAnswer'; +import StorageIcon from '@mui/icons-material/Storage'; +import MemoryIcon from '@mui/icons-material/Memory'; + +// 默认模型列表 +const mockModels = [ + { id: 'deepseek-r1', provider: 'Ollama', name: 'DeepSeek-R1' }, + { id: 'gpt-3.5-turbo-openai', provider: 'OpenAI', name: 'gpt-3.5-turbo' }, + { id: 'gpt-3.5-turbo-guiji', provider: 'Guiji', name: 'gpt-3.5-turbo' }, + { id: 'glm-4-flash', provider: 'Zhipu AI', name: 'GLM-4-Flash' } +]; + +export default function StatsCard({ projects }) { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + + // 统计卡片数据 + const statsItems = [ + { + value: projects.length, + label: t('stats.ongoingProjects'), + color: 'primary', + icon: + }, + { + value: projects.reduce((sum, project) => sum + (project.questionsCount || 0), 0), + label: t('stats.questionCount'), + color: 'secondary', + icon: + }, + { + value: projects.reduce((sum, project) => sum + (project.datasetsCount || 0), 0), + label: t('stats.generatedDatasets'), + color: 'success', + icon: + }, + { + value: mockModels.length, + label: t('stats.supportedModels'), + color: 'warning', + icon: + } + ]; + + return ( + + + {statsItems.map((item, index) => ( + + + + {item.icon} + + + {item.value} + + + {item.label} + + + + ))} + + + ); +} diff --git a/easy-dataset-main/components/mga/GaPairsIndicator.js b/easy-dataset-main/components/mga/GaPairsIndicator.js new file mode 100644 index 0000000..d2329ab --- /dev/null +++ b/easy-dataset-main/components/mga/GaPairsIndicator.js @@ -0,0 +1,151 @@ +'use client'; + +import { useState, useEffect, useCallback, useRef } from 'react'; +import { + Box, + Chip, + Typography, + IconButton, + Tooltip, + CircularProgress, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button +} from '@mui/material'; +import { Psychology as PsychologyIcon, AutoAwesome as AutoFixIcon } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import GaPairsManager from './GaPairsManager'; + +/** + * GA Pairs Indicator Component - Shows GA pairs status for a file + * @param {Object} props + * @param {string} props.projectId - Project ID + * @param {string} props.fileId - File ID + * @param {string} props.fileName - File name for display + */ +export default function GaPairsIndicator({ projectId, fileId, fileName = '未命名文件' }) { + const { t } = useTranslation(); + const [gaPairs, setGaPairs] = useState([]); + const [loading, setLoading] = useState(false); + const [detailsOpen, setDetailsOpen] = useState(false); + + // 获取GA对状态的函数 + const fetchGaPairsStatus = useCallback(async () => { + try { + setLoading(true); + + const response = await fetch(`/api/projects/${projectId}/files/${fileId}/ga-pairs`); + + if (!response.ok) { + if (response.status === 404) { + setGaPairs([]); + return; + } + throw new Error(`HTTP ${response.status}: Failed to load GA pairs`); + } + + const result = await response.json(); + + // 处理响应格式 + let newGaPairs = []; + if (Array.isArray(result)) { + newGaPairs = result; + } else if (result?.data) { + newGaPairs = result.data; + } + + setGaPairs(newGaPairs); + } catch (error) { + console.error('获取GA对状态失败:', error); + setGaPairs([]); + } finally { + setLoading(false); + } + }, [projectId, fileId]); + + // 初始加载 + useEffect(() => { + if (projectId && fileId) { + fetchGaPairsStatus(); + } + }, [projectId, fileId, fetchGaPairsStatus]); + + //监听外部事件 + useEffect(() => { + const handleRefresh = event => { + const { projectId: eventProjectId, fileIds } = event.detail || {}; + + if (eventProjectId === projectId && fileIds?.includes(String(fileId))) { + fetchGaPairsStatus(); + } + }; + + window.addEventListener('refreshGaPairsIndicators', handleRefresh); + return () => window.removeEventListener('refreshGaPairsIndicators', handleRefresh); + }, [projectId, fileId, fetchGaPairsStatus]); + + // 计算激活的GA对数量 + const activePairs = gaPairs.filter(pair => pair.isActive); + const hasGaPairs = gaPairs.length > 0; + + //GA对变化回调处理 + const handleGaPairsChange = useCallback(newGaPairs => { + setGaPairs(newGaPairs || []); + }, []); + + const handleOpenDialog = useCallback(() => { + setDetailsOpen(true); + }, []); + + const handleCloseDialog = useCallback(() => { + setDetailsOpen(false); + }, []); + + //加载状态显示 + if (loading) { + return ( + + + + Loading... + + + ); + } + + return ( + + {hasGaPairs ? ( + } + label={`${activePairs.length}/${gaPairs.length} GA Pairs`} + size="small" + color={activePairs.length > 0 ? 'primary' : 'default'} + variant={activePairs.length > 0 ? 'filled' : 'outlined'} + onClick={handleOpenDialog} + /> + ) : ( + + + + + + )} + + {/* Details Dialog */} + + GA Pairs for {fileName} + + {detailsOpen && ( + + )} + + + + + + + ); +} diff --git a/easy-dataset-main/components/mga/GaPairsManager.js b/easy-dataset-main/components/mga/GaPairsManager.js new file mode 100644 index 0000000..8ded38b --- /dev/null +++ b/easy-dataset-main/components/mga/GaPairsManager.js @@ -0,0 +1,610 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Box, + Typography, + Button, + Card, + CardContent, + Switch, + FormControlLabel, + TextField, + IconButton, + Tooltip, + Divider, + Alert, + CircularProgress, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Grid +} from '@mui/material'; +import { + Add as AddIcon, + Delete as DeleteIcon, + AutoFixHigh as AutoFixHighIcon, + Save as SaveIcon +} from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; +import i18n from '@/lib/i18n'; + +/** + * GA Pairs Manager Component + * @param {Object} props + * @param {string} props.projectId - Project ID + * @param {string} props.fileId - File ID + * @param {Function} props.onGaPairsChange - Callback when GA pairs change + */ +export default function GaPairsManager({ projectId, fileId, onGaPairsChange }) { + const { t } = useTranslation(); + const [gaPairs, setGaPairs] = useState([]); + const [backupGaPairs, setBackupGaPairs] = useState([]); // 备份状态 + const [loading, setLoading] = useState(false); + const [generating, setGenerating] = useState(false); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(null); + const [addDialogOpen, setAddDialogOpen] = useState(false); + const [newGaPair, setNewGaPair] = useState({ + genreTitle: '', + genreDesc: '', + audienceTitle: '', + audienceDesc: '', + isActive: true + }); + + useEffect(() => { + loadGaPairs(); + }, [projectId, fileId]); + + const loadGaPairs = async () => { + try { + setLoading(true); + setError(null); + + const response = await fetch(`/api/projects/${projectId}/files/${fileId}/ga-pairs`); + + // 检查响应状态 + if (!response.ok) { + if (response.status === 404) { + console.warn('GA Pairs API not found, using empty data'); + setGaPairs([]); + setBackupGaPairs([]); + return; + } + throw new Error(`HTTP ${response.status}: Failed to load GA pairs`); + } + + const result = await response.json(); + console.log('Load GA pairs result:', result); + + if (result.success) { + const loadedData = result.data || []; + setGaPairs(loadedData); + setBackupGaPairs([...loadedData]); // 创建备份 + onGaPairsChange?.(loadedData); + } else { + throw new Error(result.error || 'Failed to load GA pairs'); + } + } catch (error) { + console.error('Load GA pairs error:', error); + setError(t('gaPairs.loadError', { error: error.message })); + } finally { + setLoading(false); + } + }; + + const generateGaPairs = async () => { + try { + setGenerating(true); + setError(null); + + console.log('Starting GA pairs generation...'); + + // Get current language from i18n + const currentLanguage = i18n.language === 'en' ? 'en' : '中文'; + + const response = await fetch(`/api/projects/${projectId}/files/${fileId}/ga-pairs`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + regenerate: false, + appendMode: true, // 新增:启用追加模式 + language: currentLanguage + }) + }); + + if (!response.ok) { + let errorMessage = t('gaPairs.generateError'); + + if (response.status === 404) { + errorMessage = t('gaPairs.serviceNotAvailable'); + } else if (response.status === 400) { + try { + const errorResult = await response.json(); + if (errorResult.error?.includes('No active AI model')) { + errorMessage = t('gaPairs.noActiveModel'); + } else if (errorResult.error?.includes('content might be too short')) { + errorMessage = t('gaPairs.contentTooShort'); + } else { + errorMessage = errorResult.error || errorMessage; + } + } catch (parseError) { + errorMessage = t('gaPairs.requestFailed', { status: response.status }); + } + } else if (response.status === 500) { + try { + const errorResult = await response.json(); + if (errorResult.error?.includes('model configuration') || errorResult.error?.includes('Module not found')) { + errorMessage = t('gaPairs.configError'); + } else { + errorMessage = errorResult.error || 'Internal server error occurred.'; + } + } catch (parseError) { + console.error('Failed to parse error response:', parseError); + errorMessage = errorResult.error || t('gaPairs.internalServerError'); + } + } + + throw new Error(errorMessage); + } + + // 处理成功响应 + const responseText = await response.text(); + if (!responseText || responseText.trim() === '') { + throw new Error(t('gaPairs.emptyResponse')); + } + + const result = JSON.parse(responseText); + console.log('Generate GA pairs result:', result); + + if (result.success) { + // 在追加模式下,后端只返回新生成的GA对 + const newGaPairs = result.data || []; + + // 将新生成的GA对追加到现有的GA对 + const updatedGaPairs = [...gaPairs, ...newGaPairs]; + + setGaPairs(updatedGaPairs); + setBackupGaPairs([...updatedGaPairs]); // 更新备份 + onGaPairsChange?.(updatedGaPairs); + setSuccess( + t('gaPairs.additionalPairsGenerated', { + count: newGaPairs.length, + total: updatedGaPairs.length + }) + ); + } else { + throw new Error(result.error || t('gaPairs.generationFailed')); + } + } catch (error) { + console.error('Generate GA pairs error:', error); + setError(error.message); + } finally { + setGenerating(false); + } + }; + + const saveGaPairs = async () => { + try { + setSaving(true); + setError(null); + + // 验证GA对数据 + const validatedGaPairs = gaPairs.map((pair, index) => { + // 处理不同的数据格式 + let genreTitle, genreDesc, audienceTitle, audienceDesc; + + if (pair.genre && typeof pair.genre === 'object') { + genreTitle = pair.genre.title; + genreDesc = pair.genre.description; + } else { + genreTitle = pair.genreTitle || pair.genre; + genreDesc = pair.genreDesc || ''; + } + + if (pair.audience && typeof pair.audience === 'object') { + audienceTitle = pair.audience.title; + audienceDesc = pair.audience.description; + } else { + audienceTitle = pair.audienceTitle || pair.audience; + audienceDesc = pair.audienceDesc || ''; + } + + // 验证必填字段 + if (!genreTitle || !audienceTitle) { + throw new Error(t('gaPairs.validationError', { number: index + 1 })); + } + + return { + id: pair.id, + genreTitle: genreTitle.trim(), + genreDesc: genreDesc.trim(), + audienceTitle: audienceTitle.trim(), + audienceDesc: audienceDesc.trim(), + isActive: pair.isActive !== undefined ? pair.isActive : true + }; + }); + + console.log('Saving validated GA pairs:', validatedGaPairs); + + const response = await fetch(`/api/projects/${projectId}/files/${fileId}/ga-pairs`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + updates: validatedGaPairs + }) + }); + + if (!response.ok) { + let errorMessage = t('gaPairs.saveError'); + + if (response.status === 404) { + errorMessage = 'GA Pairs save service is not available.'; + } else { + try { + const errorResult = await response.json(); + errorMessage = errorResult.error || errorMessage; + } catch (parseError) { + errorMessage = t('gaPairs.serverError', { status: response.status }); + } + } + + throw new Error(errorMessage); + } + + const responseText = await response.text(); + const result = responseText ? JSON.parse(responseText) : { success: true }; + + if (result.success) { + // 更新本地状态为服务器返回的数据 + const savedData = result.data || validatedGaPairs; + setGaPairs(savedData); + + // 根据保存的GA对数量显示不同的成功消息 + if (savedData.length === 0) { + setSuccess(t('gaPairs.allPairsDeleted')); + } else { + setSuccess(t('gaPairs.pairsSaved', { count: savedData.length })); + } + + onGaPairsChange?.(savedData); + } else { + throw new Error(result.error || t('gaPairs.saveOperationFailed')); + } + } catch (error) { + console.error('Save GA pairs error:', error); + setError(error.message); + } finally { + setSaving(false); + } + }; + + const handleGaPairChange = (index, field, value) => { + const updatedGaPairs = [...gaPairs]; + + // 确保对象存在 + if (!updatedGaPairs[index]) { + console.error(`GA pair at index ${index} does not exist`); + return; + } + + updatedGaPairs[index] = { + ...updatedGaPairs[index], + [field]: value + }; + + setGaPairs(updatedGaPairs); + // 不立即调用 onGaPairsChange,等用户点击保存时再调用 + }; + + const handleDeleteGaPair = index => { + const updatedGaPairs = gaPairs.filter((_, i) => i !== index); + setGaPairs(updatedGaPairs); + onGaPairsChange?.(updatedGaPairs); + }; + + const handleAddGaPair = () => { + // 验证输入 + if (!newGaPair.genreTitle?.trim() || !newGaPair.audienceTitle?.trim()) { + setError(t('gaPairs.requiredFields')); + return; + } + + // 创建新的GA对对象 + const newPair = { + id: `temp_${Date.now()}`, // 临时ID + genreTitle: newGaPair.genreTitle.trim(), + genreDesc: newGaPair.genreDesc?.trim() || '', + audienceTitle: newGaPair.audienceTitle.trim(), + audienceDesc: newGaPair.audienceDesc?.trim() || '', + isActive: true + }; + + const updatedGaPairs = [...gaPairs, newPair]; + setGaPairs(updatedGaPairs); + onGaPairsChange?.(updatedGaPairs); + + // 重置表单并关闭对话框 + setNewGaPair({ + genreTitle: '', + genreDesc: '', + audienceTitle: '', + audienceDesc: '', + isActive: true + }); + setAddDialogOpen(false); + setError(null); + }; + + const resetMessages = () => { + setError(null); + setSuccess(null); + }; + + const recoverFromBackup = () => { + setGaPairs([...backupGaPairs]); + setError(null); + setSuccess(t('gaPairs.restoredFromBackup')); + }; + + useEffect(() => { + if (error || success) { + const timer = setTimeout(resetMessages, 5000); + return () => clearTimeout(timer); + } + }, [error, success]); + + if (loading) { + return ( + + + {t('gaPairs.loading')} + + ); + } + + return ( + + {/* Header with action buttons */} + + {t('gaPairs.title')} + + {/* 右上角按钮为手动添加GA对 */} + + + + + + {/* Error/Success Messages */} + {error && ( + 0 && ( + + ) + } + onClose={resetMessages} + > + {error} + + )} + {success && ( + + {success} + + )} + + {/* Generate GA Pairs Section - 只在没有GA对时显示 */} + {gaPairs.length === 0 && ( + + + + {t('gaPairs.noGaPairsTitle')} + + + {t('gaPairs.noGaPairsDescription')} + + + + + )} + + {/* GA Pairs List */} + {gaPairs.length > 0 && ( + + + {t('gaPairs.activePairs', { + active: gaPairs.filter(pair => pair.isActive).length, + total: gaPairs.length + })} + + + + {gaPairs.map((pair, index) => ( + + + + + + {t('gaPairs.pairNumber', { number: index + 1 })} + + + handleGaPairChange(index, 'isActive', e.target.checked)} + size="small" + /> + } + label={t('gaPairs.active')} + /> + {/* 添加删除按钮 */} + + handleDeleteGaPair(index)}> + + + + + + + + handleGaPairChange(index, 'genreTitle', e.target.value)} + multiline + rows={2} + fullWidth + disabled={!pair.isActive} + /> + handleGaPairChange(index, 'genreDesc', e.target.value)} + multiline + rows={2} + fullWidth + disabled={!pair.isActive} + /> + handleGaPairChange(index, 'audienceTitle', e.target.value)} + multiline + rows={2} + fullWidth + disabled={!pair.isActive} + /> + handleGaPairChange(index, 'audienceDesc', e.target.value)} + multiline + rows={2} + fullWidth + disabled={!pair.isActive} + /> + + + + + ))} + + + {/* 在GA对列表下方添加生成按钮 */} + + + + + )} + + {/* Add GA Pair Dialog */} + setAddDialogOpen(false)} maxWidth="md" fullWidth> + {t('gaPairs.addDialogTitle')} + + + setNewGaPair({ ...newGaPair, genreTitle: e.target.value })} + fullWidth + required + placeholder={t('gaPairs.genreTitlePlaceholder')} + /> + setNewGaPair({ ...newGaPair, genreDesc: e.target.value })} + multiline + rows={3} + fullWidth + placeholder={t('gaPairs.genreDescPlaceholder')} + /> + setNewGaPair({ ...newGaPair, audienceTitle: e.target.value })} + fullWidth + required + placeholder={t('gaPairs.audienceTitlePlaceholder')} + /> + setNewGaPair({ ...newGaPair, audienceDesc: e.target.value })} + multiline + rows={3} + fullWidth + placeholder={t('gaPairs.audienceDescPlaceholder')} + /> + setNewGaPair({ ...newGaPair, isActive: e.target.checked })} + /> + } + label={t('gaPairs.active')} + /> + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/playground/ChatArea.js b/easy-dataset-main/components/playground/ChatArea.js new file mode 100644 index 0000000..927c5e9 --- /dev/null +++ b/easy-dataset-main/components/playground/ChatArea.js @@ -0,0 +1,83 @@ +'use client'; + +import React, { useRef, useEffect } from 'react'; +import { Box, Typography, Paper, Grid, CircularProgress } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import ChatMessage from './ChatMessage'; +import { playgroundStyles } from '@/styles/playground'; +import { useTranslation } from 'react-i18next'; + +const ChatArea = ({ selectedModels, conversations, loading, getModelName }) => { + const theme = useTheme(); + const styles = playgroundStyles(theme); + const { t } = useTranslation(); + + // 为每个模型创建独立的引用 + const chatContainerRefs = { + model1: useRef(null), + model2: useRef(null), + model3: useRef(null) + }; + + // 为每个模型的聊天容器自动滚动到底部 + useEffect(() => { + Object.values(chatContainerRefs).forEach(ref => { + if (ref.current) { + ref.current.scrollTop = ref.current.scrollHeight; + } + }); + }, [conversations]); + + if (selectedModels.length === 0) { + return ( + + {t('playground.selectModelFirst')} + + ); + } + + return ( + + {selectedModels.map((modelId, index) => { + const modelConversation = conversations[modelId] || []; + const isLoading = loading[modelId]; + const refKey = `model${index + 1}`; + + return ( + 1 ? 12 / selectedModels.length : 12} + key={modelId} + style={{ maxHeight: 'calc(100vh - 300px)' }} + > + + + {getModelName(modelId)} + {isLoading && } + + + + {modelConversation.length === 0 ? ( + + + {t('playground.sendFirstMessage')} + + + ) : ( + modelConversation.map((message, msgIndex) => ( + + + + )) + )} + + + + ); + })} + + ); +}; + +export default ChatArea; diff --git a/easy-dataset-main/components/playground/ChatMessage.js b/easy-dataset-main/components/playground/ChatMessage.js new file mode 100644 index 0000000..faeb32a --- /dev/null +++ b/easy-dataset-main/components/playground/ChatMessage.js @@ -0,0 +1,215 @@ +import React, { useState } from 'react'; +import { Box, Paper, Typography, Alert, useTheme, IconButton, Collapse } from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import PsychologyIcon from '@mui/icons-material/Psychology'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import { useTranslation } from 'react-i18next'; + +/** + * 聊天消息组件 + * @param {Object} props + * @param {Object} props.message - 消息对象 + * @param {string} props.message.role - 消息角色:'user'、'assistant' 或 'error' + * @param {string} props.message.content - 消息内容 + * @param {string} props.modelName - 模型名称(仅在 assistant 或 error 类型消息中显示) + */ +export default function ChatMessage({ message, modelName }) { + const theme = useTheme(); + const { t } = useTranslation(); + + // 用户消息 + if (message.role === 'user') { + return ( + + + {typeof message.content === 'string' ? ( + {message.content} + ) : ( + // 如果是数组类型(用于视觉模型的用户输入) + <> + {Array.isArray(message.content) && + message.content.map((item, i) => { + if (item.type === 'text') { + return ( + + {item.text} + + ); + } else if (item.type === 'image_url') { + return ( + + 上传图片 + + ); + } + return null; + })} + + )} + + + ); + } + + // 助手消息 + if (message.role === 'assistant') { + // 处理推理过程的展示状态 + const [showThinking, setShowThinking] = useState(message.showThinking || false); + const hasThinking = message.thinking && message.thinking.trim().length > 0; + + return ( + + + {modelName && ( + + {modelName} + + )} + + {/* 推理过程显示区域 */} + {hasThinking && ( + + + + {message.isStreaming ? ( + + ) : ( + + )} + + {t('playground.reasoningProcess', '推理过程')} + + + setShowThinking(!showThinking)} sx={{ p: 0 }}> + {showThinking ? : } + + + + + + + {message.thinking} + + + + + )} + + {/* 回答内容 */} + + {typeof message.content === 'string' ? ( + <> + {message.content} + {message.isStreaming && |} + + ) : ( + // 如果是数组类型(用于视觉模型的响应) + <> + {Array.isArray(message.content) && + message.content.map((item, i) => { + if (item.type === 'text') { + return {item.text}; + } else if (item.type === 'image_url') { + return ( + + 图片 + + ); + } + return null; + })} + {message.isStreaming && |} + + )} + + + + ); + } + + // 错误消息 + if (message.role === 'error') { + return ( + + + {modelName && ( + + {modelName} + + )} + {message.content} + + + ); + } + + return null; +} diff --git a/easy-dataset-main/components/playground/MessageInput.js b/easy-dataset-main/components/playground/MessageInput.js new file mode 100644 index 0000000..ddfe18c --- /dev/null +++ b/easy-dataset-main/components/playground/MessageInput.js @@ -0,0 +1,104 @@ +'use client'; + +import React, { useState } from 'react'; +import { Box, TextField, Button, IconButton, Badge, Tooltip } from '@mui/material'; +import SendIcon from '@mui/icons-material/Send'; +import ImageIcon from '@mui/icons-material/Image'; +import CancelIcon from '@mui/icons-material/Cancel'; +import { useTheme } from '@mui/material/styles'; +import { playgroundStyles } from '@/styles/playground'; +import { useTranslation } from 'react-i18next'; + +const MessageInput = ({ + userInput, + handleInputChange, + handleSendMessage, + loading, + selectedModels, + uploadedImage, + handleImageUpload, + handleRemoveImage, + availableModels +}) => { + const theme = useTheme(); + const styles = playgroundStyles(theme); + const { t } = useTranslation(); + + const isDisabled = Object.values(loading).some(value => value) || selectedModels.length === 0; + const isSendDisabled = isDisabled || (!userInput.trim() && !uploadedImage); + + // 检查是否有视觉模型被选中 + const hasVisionModel = selectedModels.some(modelId => { + const model = availableModels.find(m => m.id === modelId); + return model && model.type === 'vision'; + }); + + return ( + + {uploadedImage && ( + + + + + } + sx={{ width: '100%' }} + overlap="rectangular" + anchorOrigin={{ vertical: 'top', horizontal: 'right' }} + > + 上传图片 + + + )} + + { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }} + multiline + maxRows={4} + /> + {hasVisionModel && ( + + + + + + + + + )} + + + + ); +}; + +export default MessageInput; diff --git a/easy-dataset-main/components/playground/ModelSelector.js b/easy-dataset-main/components/playground/ModelSelector.js new file mode 100644 index 0000000..f34a794 --- /dev/null +++ b/easy-dataset-main/components/playground/ModelSelector.js @@ -0,0 +1,81 @@ +import React from 'react'; +import { + FormControl, + InputLabel, + Select, + MenuItem, + OutlinedInput, + Box, + Chip, + Checkbox, + ListItemText +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; +const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250 + } + } +}; + +/** + * 模型选择组件 + * @param {Object} props + * @param {Array} props.models - 可用模型列表 + * @param {Array} props.selectedModels - 已选择的模型ID列表 + * @param {Function} props.onChange - 选择改变时的回调函数 + */ +export default function ModelSelector({ models, selectedModels, onChange }) { + // 获取模型名称 + const getModelName = modelId => { + const model = models.find(m => m.id === modelId); + return model ? `${model.providerName}: ${model.modelName}` : modelId; + }; + const { t } = useTranslation(); + + return ( + + {t('playground.selectModelMax3')} + + + ); +} diff --git a/easy-dataset-main/components/playground/PlaygroundHeader.js b/easy-dataset-main/components/playground/PlaygroundHeader.js new file mode 100644 index 0000000..75cb9ca --- /dev/null +++ b/easy-dataset-main/components/playground/PlaygroundHeader.js @@ -0,0 +1,66 @@ +'use client'; + +import React from 'react'; +import { Grid, Button, Divider, FormControl, InputLabel, Select, MenuItem } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { useTheme } from '@mui/material/styles'; +import ModelSelector from './ModelSelector'; +import { playgroundStyles } from '@/styles/playground'; +import { useTranslation } from 'react-i18next'; + +const PlaygroundHeader = ({ + availableModels, + selectedModels, + handleModelSelection, + handleClearConversations, + conversations, + outputMode, + handleOutputModeChange +}) => { + const theme = useTheme(); + const styles = playgroundStyles(theme); + const { t } = useTranslation(); + + const isClearDisabled = selectedModels.length === 0 || Object.values(conversations).every(conv => conv.length === 0); + + return ( + <> + + + + + + + {t('playground.outputMode')} + + + + + + + + + + + ); +}; + +export default PlaygroundHeader; diff --git a/easy-dataset-main/components/questions/QuestionListView.js b/easy-dataset-main/components/questions/QuestionListView.js new file mode 100644 index 0000000..0d1bf56 --- /dev/null +++ b/easy-dataset-main/components/questions/QuestionListView.js @@ -0,0 +1,374 @@ +'use client'; + +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Box, + Typography, + Checkbox, + IconButton, + Chip, + Tooltip, + Pagination, + Divider, + Paper, + CircularProgress, + TextField +} from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import EditIcon from '@mui/icons-material/Edit'; +import ChatIcon from '@mui/icons-material/Chat'; +import { useGenerateDataset } from '@/hooks/useGenerateDataset'; +import { toast } from 'sonner'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; + +export default function QuestionListView({ + questions = [], + currentPage, + totalQuestions = 0, + handlePageChange, + selectedQuestions = [], + onSelectQuestion, + onDeleteQuestion, + projectId, + onEditQuestion, + refreshQuestions +}) { + const { t } = useTranslation(); + // 处理状态 + const [processingQuestions, setProcessingQuestions] = useState({}); + const { generateSingleDataset } = useGenerateDataset(); + // 获取当前选中的模型 + const selectedModelInfo = useAtomValue(selectedModelInfoAtom); + + // 获取文本块的标题 + const getChunkTitle = content => { + const firstLine = content ? content.split('\n')[0].trim() : ''; + if (firstLine.startsWith('# ')) { + return firstLine.substring(2); + } else if (firstLine.length > 0) { + return firstLine.length > 200 ? firstLine.substring(0, 200) + '...' : firstLine; + } + return ''; + }; + + // 检查问题是否被选中 + const isQuestionSelected = questionId => { + return selectedQuestions.includes(questionId); + }; + + // 处理生成数据集 + const handleGenerateDataset = async (questionId, questionInfo, imageId, imageName) => { + // 设置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [questionId]: true + })); + await generateSingleDataset({ + projectId, + questionId, + questionInfo, + imageId, + imageName + }); + // 重置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [questionId]: false + })); + refreshQuestions(); + }; + + // 处理生成多轮对话数据集 + const handleGenerateMultiTurnDataset = async (questionId, questionInfo) => { + try { + // 设置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [`${questionId}_multi`]: true + })); + + // 首先检查项目是否配置了多轮对话设置 + const configResponse = await fetch(`/api/projects/${projectId}/tasks`); + if (!configResponse.ok) { + throw new Error('获取项目配置失败'); + } + + const config = await configResponse.json(); + const multiTurnConfig = { + systemPrompt: config.multiTurnSystemPrompt, + scenario: config.multiTurnScenario, + rounds: config.multiTurnRounds, + roleA: config.multiTurnRoleA, + roleB: config.multiTurnRoleB + }; + + console.log('multiTurnConfig:', multiTurnConfig); + + // 检查是否已配置必要的多轮对话设置 + // 系统提示词是可选的,但场景、角色A、角色B和轮数是必需的 + if ( + !multiTurnConfig.scenario || + !multiTurnConfig.roleA || + !multiTurnConfig.roleB || + !multiTurnConfig.rounds || + multiTurnConfig.rounds < 1 + ) { + toast.error(t('questions.multiTurnNotConfigured', '请先在项目设置中配置多轮对话相关参数')); + return; + } + + // 检查是否选中了模型 + if (!selectedModelInfo) { + toast.error(t('datasets.selectModelFirst', '请先选择模型')); + return; + } + + // 调用多轮对话生成API + const response = await fetch(`/api/projects/${projectId}/dataset-conversations`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + questionId, + ...multiTurnConfig, + model: selectedModelInfo + }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.message || '生成多轮对话数据集失败'); + } + + const result = await response.json(); + toast.success(t('questions.multiTurnGenerated', '多轮对话数据集生成成功!')); + } catch (error) { + console.error('生成多轮对话数据集失败:', error); + toast.error(error.message || '生成多轮对话数据集失败'); + } finally { + // 重置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [`${questionId}_multi`]: false + })); + } + }; + + return ( + + {/* 问题列表 */} + + + + {t('datasets.question')} + + + + {t('common.label')} + + + {t('common.dataSource')} + + + {t('common.actions')} + + + + + + + {questions.map((question, index) => { + const isSelected = isQuestionSelected(question.id); + const questionKey = question.id; + return ( + + + { + onSelectQuestion(questionKey); + }} + size="small" + /> + + + + {question.question} + {question.datasetCount > 0 ? ( + + ) : null} + + + {question.label || t('datasets.noTag')} • ID: {(question.question || '').substring(0, 8)} + + + + + {question.label ? ( + + ) : ( + + {t('datasets.noTag')} + + )} + + + + + + + + + + + onEditQuestion(question)} + disabled={processingQuestions[questionKey]} + > + + + + + + handleGenerateDataset(question.id, question.question, question.imageId, question.imageName) + } + disabled={processingQuestions[questionKey]} + > + {processingQuestions[questionKey] ? ( + + ) : ( + + )} + + + + {!question.imageId && ( + + handleGenerateMultiTurnDataset(question.id, question.question)} + disabled={processingQuestions[`${questionKey}_multi`]} + > + {processingQuestions[`${questionKey}_multi`] ? ( + + ) : ( + + )} + + + )} + + + onDeleteQuestion(question.id)} + disabled={processingQuestions[questionKey]} + > + + + + + + {index < questions.length - 1 && } + + ); + })} + + + {/* 分页 */} + {totalQuestions > 1 && ( + + + + {t('common.jumpTo')}: + { + if (e.key === 'Enter') { + const pageNum = parseInt(e.target.value, 10); + if (pageNum >= 1 && pageNum <= totalQuestions) { + handlePageChange(null, pageNum); + e.target.value = ''; + } + } + }} + /> + + + )} + + ); +} diff --git a/easy-dataset-main/components/questions/QuestionTreeView.js b/easy-dataset-main/components/questions/QuestionTreeView.js new file mode 100644 index 0000000..6688742 --- /dev/null +++ b/easy-dataset-main/components/questions/QuestionTreeView.js @@ -0,0 +1,565 @@ +'use client'; + +import { useState, useEffect, useCallback, useMemo, memo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Box, + Typography, + Paper, + List, + ListItem, + ListItemText, + Checkbox, + IconButton, + Collapse, + Chip, + Tooltip, + Divider, + CircularProgress +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ExpandLessIcon from '@mui/icons-material/ExpandLess'; +import DeleteIcon from '@mui/icons-material/Delete'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import EditIcon from '@mui/icons-material/Edit'; +import FolderIcon from '@mui/icons-material/Folder'; +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import { useGenerateDataset } from '@/hooks/useGenerateDataset'; +import axios from 'axios'; + +/** + * 问题树视图组件 + * @param {Object} props + * @param {Array} props.tags - 标签树 + * @param {Array} props.selectedQuestions - 已选择的问题ID列表 + * @param {Function} props.onSelectQuestion - 选择问题的回调函数 + * @param {Function} props.onDeleteQuestion - 删除问题的回调函数 + */ +export default function QuestionTreeView({ + tags = [], + selectedQuestions = [], + onSelectQuestion, + onDeleteQuestion, + onEditQuestion, + projectId, + searchTerm +}) { + const { t } = useTranslation(); + const [expandedTags, setExpandedTags] = useState({}); + const [questionsByTag, setQuestionsByTag] = useState({}); + const [processingQuestions, setProcessingQuestions] = useState({}); + const { generateSingleDataset } = useGenerateDataset(); + const [questions, setQuestions] = useState([]); + const [loadedTags, setLoadedTags] = useState({}); + // 初始化时,将所有标签设置为收起状态(而不是展开状态) + useEffect(() => { + async function fetchTagsInfo() { + try { + // 获取标签信息,仅用于标签统计 + const response = await axios.get(`/api/projects/${projectId}/questions/tree?tagsOnly=true&input=${searchTerm}`); + setQuestions(response.data); // 设置数据仅用于标签统计 + + // 当搜索条件变化时,重新加载已展开标签的问题数据 + const expandedTagLabels = Object.entries(expandedTags) + .filter(([_, isExpanded]) => isExpanded) + .map(([label]) => label); + + // 重新加载已展开标签的数据 + for (const label of expandedTagLabels) { + fetchTagQuestions(label); + } + } catch (error) { + console.error('获取标签信息失败:', error); + } + } + + if (projectId) { + fetchTagsInfo(); + } + + const initialExpandedState = {}; + const processTag = tag => { + // 将默认状态改为 false(收起)而不是 true(展开) + initialExpandedState[tag.label] = false; + if (tag.child && tag.child.length > 0) { + tag.child.forEach(processTag); + } + }; + + tags.forEach(processTag); + // 未分类问题也默认收起 + initialExpandedState['uncategorized'] = false; + setExpandedTags(initialExpandedState); + }, [tags]); + + // 根据标签对问题进行分类 + useEffect(() => { + const taggedQuestions = {}; + + // 初始化标签映射 + const initTagMap = tag => { + taggedQuestions[tag.label] = []; + if (tag.child && tag.child.length > 0) { + tag.child.forEach(initTagMap); + } + }; + + tags.forEach(initTagMap); + + // 将问题分配到对应的标签下 + questions.forEach(question => { + // 如果问题没有标签,添加到"未分类" + if (!question.label) { + if (!taggedQuestions['uncategorized']) { + taggedQuestions['uncategorized'] = []; + } + taggedQuestions['uncategorized'].push(question); + return; + } + + // 将问题添加到匹配的标签下 + const questionLabel = question.label; + + // 查找最精确匹配的标签 + // 使用一个数组来存储所有匹配的标签路径,以便找到最精确的匹配 + const findAllMatchingTags = (tag, path = []) => { + const currentPath = [...path, tag.label]; + + // 存储所有匹配结果 + const matches = []; + + // 精确匹配当前标签 + if (tag.label === questionLabel) { + matches.push({ label: tag.label, depth: currentPath.length }); + } + + // 检查子标签 + if (tag.child && tag.child.length > 0) { + for (const childTag of tag.child) { + const childMatches = findAllMatchingTags(childTag, currentPath); + matches.push(...childMatches); + } + } + + return matches; + }; + + // 在所有根标签中查找所有匹配 + let allMatches = []; + for (const rootTag of tags) { + const matches = findAllMatchingTags(rootTag); + allMatches.push(...matches); + } + + // 找到深度最大的匹配(最精确的匹配) + let matchedTagLabel = null; + if (allMatches.length > 0) { + // 按深度排序,深度最大的是最精确的匹配 + allMatches.sort((a, b) => b.depth - a.depth); + matchedTagLabel = allMatches[0].label; + } + + if (matchedTagLabel) { + // 如果找到匹配的标签,将问题添加到该标签下 + if (!taggedQuestions[matchedTagLabel]) { + taggedQuestions[matchedTagLabel] = []; + } + taggedQuestions[matchedTagLabel].push(question); + } else { + // 如果找不到匹配的标签,添加到"未分类" + if (!taggedQuestions['uncategorized']) { + taggedQuestions['uncategorized'] = []; + } + taggedQuestions['uncategorized'].push(question); + } + }); + + setQuestionsByTag(taggedQuestions); + }, [questions, tags]); + + // 处理展开/折叠标签 - 使用 useCallback 优化 + const handleToggleExpand = useCallback( + tagLabel => { + // 检查是否需要加载此标签的问题数据 + const shouldExpand = !expandedTags[tagLabel]; + + if (shouldExpand && !loadedTags[tagLabel]) { + // 如果要展开且尚未加载数据,则加载数据 + fetchTagQuestions(tagLabel); + } + + setExpandedTags(prev => ({ + ...prev, + [tagLabel]: shouldExpand + })); + }, + [expandedTags, loadedTags, projectId] + ); + + // 获取特定标签的问题数据 + const fetchTagQuestions = useCallback( + async tagLabel => { + try { + const response = await axios.get( + `/api/projects/${projectId}/questions/tree?tag=${encodeURIComponent(tagLabel)}${searchTerm ? `&input=${searchTerm}` : ''}` + ); + + // 更新问题数据,合并新获取的数据 + setQuestions(prev => { + // 创建一个新数组,包含现有数据 + const updatedQuestions = [...prev]; + + // 添加新获取的问题数据 + response.data.forEach(newQuestion => { + // 检查是否已存在相同 ID 的问题 + const existingIndex = updatedQuestions.findIndex(q => q.id === newQuestion.id); + if (existingIndex === -1) { + // 如果不存在,添加到数组 + updatedQuestions.push(newQuestion); + } else { + // 如果已存在,更新数据 + updatedQuestions[existingIndex] = newQuestion; + } + }); + + return updatedQuestions; + }); + + // 标记该标签已加载数据 + setLoadedTags(prev => ({ + ...prev, + [tagLabel]: true + })); + } catch (error) { + console.error(`获取标签 "${tagLabel}" 的问题失败:`, error); + } + }, + [projectId, searchTerm, expandedTags] + ); + + // 检查问题是否被选中 - 使用 useCallback 优化 + const isQuestionSelected = useCallback( + questionKey => { + return selectedQuestions.includes(questionKey); + }, + [selectedQuestions] + ); + + // 处理生成数据集 - 使用 useCallback 优化 + const handleGenerateDataset = async (questionId, questionInfo) => { + // 设置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [questionId]: true + })); + await generateSingleDataset({ projectId, questionId, questionInfo }); + // 重置处理状态 + setProcessingQuestions(prev => ({ + ...prev, + [questionId]: false + })); + }; + + // 渲染单个问题项 - 使用 useCallback 优化 + const renderQuestionItem = useCallback( + (question, index, total) => { + const questionKey = question.id; + return ( + + ); + }, + [isQuestionSelected, onSelectQuestion, onDeleteQuestion, handleGenerateDataset, processingQuestions, t] + ); + + // 计算标签及其子标签下的所有问题数量 - 使用 useMemo 缓存计算结果 + const tagQuestionCounts = useMemo(() => { + const counts = {}; + + const countQuestions = tag => { + const directQuestions = questionsByTag[tag.label] || []; + let total = directQuestions.length; + + if (tag.child && tag.child.length > 0) { + for (const childTag of tag.child) { + total += countQuestions(childTag); + } + } + + counts[tag.label] = total; + return total; + }; + + tags.forEach(countQuestions); + return counts; + }, [questionsByTag, tags]); + + // 递归渲染标签树 - 使用 useCallback 优化 + const renderTagTree = useCallback( + (tag, level = 0) => { + const questions = questionsByTag[tag.label] || []; + const hasQuestions = questions.length > 0; + const hasChildren = tag.child && tag.child.length > 0; + const isExpanded = expandedTags[tag.label]; + const totalQuestions = tagQuestionCounts[tag.label] || 0; + + return ( + + + + {/* 只有当标签展开时才渲染子内容,减少不必要的渲染 */} + {isExpanded && ( + + {hasChildren && ( + {tag.child.map(childTag => renderTagTree(childTag, level + 1))} + )} + + {hasQuestions && ( + + {questions.map((question, index) => renderQuestionItem(question, index, questions.length))} + + )} + + )} + + ); + }, + [questionsByTag, expandedTags, tagQuestionCounts, handleToggleExpand, renderQuestionItem, t] + ); + + // 渲染未分类问题 + const renderUncategorizedQuestions = () => { + const uncategorizedQuestions = questionsByTag['uncategorized'] || []; + if (uncategorizedQuestions.length === 0) return null; + + return ( + + handleToggleExpand('uncategorized')} + sx={{ + py: 1, + bgcolor: 'primary.light', + color: 'primary.contrastText', + '&:hover': { + bgcolor: 'primary.main' + }, + borderRadius: '4px', + mb: 0.5, + pr: 1 + }} + > + + + + {t('datasets.uncategorized')} + + + + } + /> + + {expandedTags['uncategorized'] ? : } + + + + + + {uncategorizedQuestions.map((question, index) => + renderQuestionItem(question, index, uncategorizedQuestions.length) + )} + + +
+ ); + }; + + // 如果没有标签和问题,显示空状态 + if (tags.length === 0 && Object.keys(questionsByTag).length === 0) { + return ( + + + {t('datasets.noTagsAndQuestions')} + + + ); + } + + return ( + + + {renderUncategorizedQuestions()} + {tags.map(tag => renderTagTree(tag))} + + + ); +} + +// 使用 memo 优化问题项渲染 +const QuestionItem = memo( + ({ question, index, total, isSelected, onSelect, onDelete, onGenerate, onEdit, isProcessing, t }) => { + const questionKey = question.id; + return ( + + + onSelect(questionKey)} size="small" /> + + + {question.question} + {question.dataSites && question.dataSites.length > 0 && ( + + )} + + } + secondary={ + + {t('datasets.source')}: {question.chunk?.name || question.chunkId || t('common.unknown')} + + } + /> + + + + onEdit({ + question: question.question, + chunkId: question.chunkId, + label: question.label || 'other' + }) + } + disabled={isProcessing} + > + + + + + onGenerate(question.id, question.question)} + disabled={isProcessing} + > + {isProcessing ? : } + + + + onDelete(question.id)}> + + + + + + {index < total - 1 && } + + ); + } +); + +// 使用 memo 优化标签项渲染 +const TagItem = memo(({ tag, level, isExpanded, totalQuestions, onToggle, t }) => { + return ( + onToggle(tag.label)} + sx={{ + pl: level * 2 + 1, + py: 1, + bgcolor: level === 0 ? 'primary.light' : 'background.paper', + color: level === 0 ? 'primary.contrastText' : 'inherit', + '&:hover': { + bgcolor: level === 0 ? 'primary.main' : 'action.hover' + }, + borderRadius: '4px', + mb: 0.5, + pr: 1 + }} + > + {/* 内部内容保持不变 */} + + + + {tag.label} + + {totalQuestions > 0 && ( + + )} +
+ } + /> + + {isExpanded ? : } + + + ); +}); diff --git a/easy-dataset-main/components/settings/BasicSettings.js b/easy-dataset-main/components/settings/BasicSettings.js new file mode 100644 index 0000000..831d447 --- /dev/null +++ b/easy-dataset-main/components/settings/BasicSettings.js @@ -0,0 +1,153 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Typography, Box, Button, TextField, Grid, Card, CardContent, Alert, Snackbar } from '@mui/material'; +import SaveIcon from '@mui/icons-material/Save'; +import { useTranslation } from 'react-i18next'; + +export default function BasicSettings({ projectId }) { + const { t } = useTranslation(); + const [projectInfo, setProjectInfo] = useState({ + id: '', + name: '', + description: '' + }); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + useEffect(() => { + async function fetchProjectInfo() { + try { + setLoading(true); + const response = await fetch(`/api/projects/${projectId}`); + + if (!response.ok) { + throw new Error(t('projects.fetchFailed')); + } + + const data = await response.json(); + setProjectInfo(data); + } catch (error) { + console.error('获取项目信息出错:', error); + setError(error.message); + } finally { + setLoading(false); + } + } + + fetchProjectInfo(); + }, [projectId, t]); + + // 处理项目信息变更 + const handleProjectInfoChange = e => { + const { name, value } = e.target; + setProjectInfo(prev => ({ + ...prev, + [name]: value + })); + }; + + // 保存项目信息 + const handleSaveProjectInfo = async () => { + try { + const response = await fetch(`/api/projects/${projectId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + name: projectInfo.name, + description: projectInfo.description + }) + }); + + if (!response.ok) { + throw new Error(t('projects.saveFailed')); + } + + setSuccess(true); + } catch (error) { + console.error('保存项目信息出错:', error); + setError(error.message); + } + }; + + const handleCloseSnackbar = () => { + setSuccess(false); + setError(null); + }; + + if (loading) { + return {t('common.loading')}; + } + + return ( + + + + {t('settings.basicInfo')} + + + + + + + + + + + + + + + + + + + + {t('settings.saveSuccess')} + + + + + + {error} + + + + ); +} diff --git a/easy-dataset-main/components/settings/ModelSettings.js b/easy-dataset-main/components/settings/ModelSettings.js new file mode 100644 index 0000000..a20d308 --- /dev/null +++ b/easy-dataset-main/components/settings/ModelSettings.js @@ -0,0 +1,1056 @@ +'use client'; + +import { useState, useEffect, useMemo } from 'react'; +import { + Typography, + Box, + Button, + TextField, + Grid, + Card, + CardContent, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + FormControl, + Autocomplete, + Slider, + InputLabel, + Select, + MenuItem, + Stack, + Paper, + Tooltip, + IconButton, + Chip, + Divider, + CircularProgress +} from '@mui/material'; +import AddIcon from '@mui/icons-material/Add'; +import CheckCircleIcon from '@mui/icons-material/CheckCircle'; +import ErrorIcon from '@mui/icons-material/Error'; +import { DEFAULT_MODEL_SETTINGS } from '@/constant/model'; +import { useTranslation } from 'react-i18next'; +import axios from 'axios'; +import { toast } from 'sonner'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import ScienceIcon from '@mui/icons-material/Science'; +import HealthAndSafetyIcon from '@mui/icons-material/HealthAndSafety'; +import { useRouter } from 'next/navigation'; +import { useAtom } from 'jotai'; +import { modelConfigListAtom, selectedModelInfoAtom } from '@/lib/store'; +import { getProviderLogo, sortProvidersByPriority } from '@/lib/util/providerLogo'; + +export default function ModelSettings({ projectId }) { + const { t } = useTranslation(); + const router = useRouter(); + // 展示端点的最大长度 + const MAX_ENDPOINT_DISPLAY = 80; + const MAX_GENERATION_TOKENS = 131072; + // 模型对话框状态 + const [openModelDialog, setOpenModelDialog] = useState(false); + const [editingModel, setEditingModel] = useState(null); + const [loading, setLoading] = useState(true); + const [providerList, setProviderList] = useState([]); + const [providerOptions, setProviderOptions] = useState([]); + const [selectedProvider, setSelectedProvider] = useState({}); + const [models, setModels] = useState([]); + const [modelConfigList, setModelConfigList] = useAtom(modelConfigListAtom); + const [selectedModelInfo, setSelectedModelInfo] = useAtom(selectedModelInfoAtom); + const orderedModelConfigList = useMemo( + () => sortProvidersByPriority(modelConfigList, item => item.providerId), + [modelConfigList] + ); + const [modelConfigForm, setModelConfigForm] = useState({ + id: '', + providerId: '', + providerName: '', + endpoint: '', + apiKey: '', + modelId: '', + modelName: '', + type: 'text', + temperature: 0.0, + maxTokens: DEFAULT_MODEL_SETTINGS.maxTokens, + topP: 0, + topK: 0, + status: 1 + }); + const [healthStatusMap, setHealthStatusMap] = useState({}); + const [batchCheckingHealth, setBatchCheckingHealth] = useState(false); + + const isModelConfigured = model => { + if (!model) return false; + const hasEndpoint = Boolean(String(model.endpoint || '').trim()); + const hasModel = Boolean(String(model.modelId || model.modelName || '').trim()); + const providerId = String(model.providerId || '').toLowerCase(); + + if (providerId === 'ollama') { + return hasEndpoint && hasModel; + } + + const hasApiKey = Boolean(String(model.apiKey || '').trim()); + return hasEndpoint && hasApiKey && hasModel; + }; + + const configuredModelList = useMemo(() => orderedModelConfigList.filter(isModelConfigured), [orderedModelConfigList]); + + const unconfiguredModelList = useMemo( + () => orderedModelConfigList.filter(model => !isModelConfigured(model)), + [orderedModelConfigList] + ); + + const normalizePositiveInteger = value => { + const parsedValue = Number(value); + if (!Number.isInteger(parsedValue) || parsedValue < 1) { + return null; + } + return parsedValue; + }; + + const getSafeMaxTokensValue = value => { + return normalizePositiveInteger(value) ?? DEFAULT_MODEL_SETTINGS.maxTokens; + }; + + useEffect(() => { + getProvidersList(); + getModelConfigList(); + }, []); + + // 获取提供商列表 + const getProvidersList = () => { + axios.get('/api/llm/providers').then(response => { + console.log('获取的模型列表', response.data); + const sortedProviders = sortProvidersByPriority(response.data, item => item.id); + setProviderList(sortedProviders); + const providerOptions = sortedProviders.map(provider => ({ + id: provider.id, + label: provider.name + })); + if (sortedProviders.length > 0) { + setSelectedProvider(sortedProviders[0]); + getProviderModels(sortedProviders[0].id); + } + setProviderOptions(providerOptions); + }); + }; + + // 裁剪端点展示长度(不改变实际值,仅用于 UI 展示) + const formatEndpoint = model => { + if (!model?.endpoint) return ''; + const base = model.endpoint.replace(/^https?:\/\//, ''); + if (base.length > MAX_ENDPOINT_DISPLAY) { + return base.slice(0, MAX_ENDPOINT_DISPLAY) + '...'; + } + return base; + }; + + // 获取模型配置列表 + const getModelConfigList = () => { + axios + .get(`/api/projects/${projectId}/model-config`) + .then(response => { + setModelConfigList(sortProvidersByPriority(response.data.data, item => item.providerId)); + setLoading(false); + }) + .catch(error => { + setLoading(false); + toast.error('Fetch model list Error'); + }); + }; + + const onChangeProvider = (event, newValue) => { + console.log('选择提供商', newValue, typeof newValue); + if (typeof newValue === 'string') { + // 用户手动输入了自定义提供商 + setModelConfigForm(prev => ({ + ...prev, + providerId: 'custom', + endpoint: '', + providerName: '' + })); + } else if (newValue && newValue.id) { + // 用户从下拉列表中选择了一个提供商 + const selectedProvider = providerList.find(p => p.id === newValue.id); + if (selectedProvider) { + setSelectedProvider(selectedProvider); + setModelConfigForm(prev => ({ + ...prev, + providerId: selectedProvider.id, + endpoint: selectedProvider.apiUrl, + providerName: selectedProvider.name, + modelName: '' + })); + getProviderModels(newValue.id); + } + } + }; + + // 获取提供商的模型列表(DB) + const getProviderModels = providerId => { + axios + .get(`/api/llm/model?providerId=${providerId}`) + .then(response => { + setModels(response.data); + }) + .catch(error => { + toast.error('Get Models Error'); + }); + }; + + // 同步模型列表 + const refreshProviderModels = async () => { + let data = await getNewModels(); + if (!data) return; + if (data.length > 0) { + setModels(data); + toast.success('Refresh Success'); + const newModelsData = await axios.post('/api/llm/model', { + newModels: data, + providerId: selectedProvider.id + }); + if (newModelsData.status === 200) { + toast.success('Get Model Success'); + } + } else { + toast.info('No Models Need Refresh'); + } + }; + + // 获取最新模型列表 + async function getNewModels() { + try { + if (!modelConfigForm || !modelConfigForm.endpoint) { + return null; + } + const providerId = modelConfigForm.providerId; + console.log(providerId, 'getNewModels providerId'); + + // 使用后端 API 代理请求 + const res = await axios.post('/api/llm/fetch-models', { + endpoint: modelConfigForm.endpoint, + providerId: providerId, + apiKey: modelConfigForm.apiKey + }); + + return res.data; + } catch (err) { + if (err.response && err.response.status === 401) { + toast.error('API Key Invalid'); + } else { + toast.error('Get Model List Error'); + } + return null; + } + } + + const getHealthCheckErrorMessage = error => { + if (error?.response?.data?.error) return String(error.response.data.error); + if (error?.response?.data?.message) return String(error.response.data.message); + if (error?.message) return String(error.message); + return t('models.endpointCheckFailed', { defaultValue: 'Endpoint check failed' }); + }; + + const checkModelEndpointHealth = async (model, { silent = false } = {}) => { + if (!model?.id) return false; + + const endpoint = String(model.endpoint || '').trim(); + if (!endpoint) { + setHealthStatusMap(prev => ({ + ...prev, + [model.id]: { + status: 'error', + message: t('models.endpointMissing', { defaultValue: 'Endpoint is empty' }) + } + })); + if (!silent) { + toast.error(t('models.endpointMissing', { defaultValue: 'Endpoint is empty' })); + } + return false; + } + + setHealthStatusMap(prev => ({ + ...prev, + [model.id]: { + status: 'checking', + message: t('models.checking', { defaultValue: 'Checking...' }) + } + })); + + try { + const response = await axios.post('/api/llm/fetch-models', { + endpoint, + providerId: model.providerId, + apiKey: model.apiKey + }); + + const resultList = Array.isArray(response.data) ? response.data : []; + const currentModelId = String(model.modelId || model.modelName || '').trim(); + const hasMatchedModel = + !currentModelId || + resultList.some(item => { + return item?.modelId === currentModelId || item?.modelName === currentModelId; + }); + + if (!hasMatchedModel) { + setHealthStatusMap(prev => ({ + ...prev, + [model.id]: { + status: 'warning', + message: t('models.endpointReachableModelMissing', { + defaultValue: 'Endpoint reachable, but current model is not in the returned model list' + }), + checkedAt: Date.now() + } + })); + if (!silent) { + toast.warning( + t('models.endpointReachableModelMissing', { + defaultValue: 'Endpoint reachable, but current model is not in the returned model list' + }) + ); + } + return true; + } + + setHealthStatusMap(prev => ({ + ...prev, + [model.id]: { + status: 'success', + message: t('models.endpointHealthy', { defaultValue: 'Endpoint is healthy' }), + checkedAt: Date.now() + } + })); + if (!silent) { + toast.success(t('models.endpointHealthy', { defaultValue: 'Endpoint is healthy' })); + } + return true; + } catch (error) { + const message = getHealthCheckErrorMessage(error); + setHealthStatusMap(prev => ({ + ...prev, + [model.id]: { + status: 'error', + message, + checkedAt: Date.now() + } + })); + if (!silent) { + toast.error(message); + } + return false; + } + }; + + const checkAllConfiguredModelHealth = async () => { + if (configuredModelList.length === 0) { + toast.info(t('models.noConfiguredModels', { defaultValue: 'No configured models to check' })); + return; + } + + setBatchCheckingHealth(true); + let okCount = 0; + let failCount = 0; + + for (const model of configuredModelList) { + const isHealthy = await checkModelEndpointHealth(model, { silent: true }); + if (isHealthy) { + okCount += 1; + } else { + failCount += 1; + } + } + + setBatchCheckingHealth(false); + toast.success( + t('models.healthCheckSummary', { + defaultValue: `Health check completed: ${okCount} healthy, ${failCount} failed`, + okCount, + failCount + }) + ); + }; + + const getHealthStatusInfo = model => { + const status = healthStatusMap[model.id]?.status || 'idle'; + const message = healthStatusMap[model.id]?.message; + + if (status === 'checking') { + return { + color: 'default', + icon: , + label: t('models.checking', { defaultValue: 'Checking...' }), + message + }; + } + + if (status === 'success') { + return { + color: 'success', + icon: , + label: t('models.healthy', { defaultValue: 'Healthy' }), + message + }; + } + + if (status === 'warning') { + return { + color: 'warning', + icon: , + label: t('models.reachable', { defaultValue: 'Reachable' }), + message + }; + } + + if (status === 'error') { + return { + color: 'error', + icon: , + label: t('models.unhealthy', { defaultValue: 'Unhealthy' }), + message + }; + } + + return { + color: 'default', + icon: , + label: t('models.notChecked', { defaultValue: 'Not checked' }), + message: t('models.notChecked', { defaultValue: 'Not checked' }) + }; + }; + + // 打开模型对话框 + const handleOpenModelDialog = (model = null) => { + if (model) { + setEditingModel(model); + console.log('handleOpenModelDialog', model); + // 兼容逻辑:如果 modelId 为空,则用 modelName 作为 modelId + const initialForm = { ...model }; + if (!initialForm.modelId && initialForm.modelName) { + initialForm.modelId = initialForm.modelName; + } + + // 编辑现有模型时,为未设置的参数应用默认值 + setModelConfigForm({ + ...initialForm, + temperature: model.temperature !== undefined ? model.temperature : DEFAULT_MODEL_SETTINGS.temperature, + maxTokens: model.maxTokens !== undefined ? model.maxTokens : DEFAULT_MODEL_SETTINGS.maxTokens, + topP: model.topP !== undefined && model.topP !== 0 ? model.topP : DEFAULT_MODEL_SETTINGS.topP + }); + getProviderModels(model.providerId); + } else { + setEditingModel(null); + // 添加新模型时,完全重置表单 + setModelConfigForm({ + providerId: selectedProvider?.id || '', + providerName: selectedProvider?.name || '', + endpoint: selectedProvider?.apiUrl || '', + apiKey: '', + modelId: '', + modelName: '', + type: 'text', + ...DEFAULT_MODEL_SETTINGS, + id: '' + }); + if (selectedProvider?.id) { + getProviderModels(selectedProvider.id); + } + } + setOpenModelDialog(true); + }; + + // 关闭模型对话框 + const handleCloseModelDialog = () => { + setEditingModel(null); + setOpenModelDialog(false); + }; + + // 处理模型表单变更 + const handleModelFormChange = e => { + const { name, value } = e.target; + console.log('handleModelFormChange', name, value); + setModelConfigForm(prev => ({ + ...prev, + [name]: value + })); + }; + + const handleMaxTokensSliderChange = (event, newValue) => { + const value = Array.isArray(newValue) ? newValue[0] : newValue; + const normalizedValue = normalizePositiveInteger(value); + if (normalizedValue === null) { + return; + } + setModelConfigForm(prev => ({ + ...prev, + maxTokens: normalizedValue + })); + }; + + const handleMaxTokensInputChange = e => { + const { value } = e.target; + if (value === '') { + setModelConfigForm(prev => ({ + ...prev, + maxTokens: '' + })); + return; + } + const normalizedValue = normalizePositiveInteger(value); + if (normalizedValue === null) { + return; + } + setModelConfigForm(prev => ({ + ...prev, + maxTokens: normalizedValue + })); + }; + + const handleMaxTokensInputBlur = () => { + const normalizedValue = normalizePositiveInteger(modelConfigForm.maxTokens); + if (normalizedValue !== null) { + return; + } + setModelConfigForm(prev => ({ + ...prev, + maxTokens: DEFAULT_MODEL_SETTINGS.maxTokens + })); + }; + + // 保存模型 + const handleSaveModel = () => { + // 确保有模型 ID + const normalizedModelId = String(modelConfigForm.modelId || '').trim(); + const normalizedModelName = String(modelConfigForm.modelName || '').trim(); + const isEditingExistingModel = Boolean(modelConfigForm.id || editingModel?.id); + + if (!isEditingExistingModel && !normalizedModelId) { + toast.error(t('models.modelIdPlaceholder')); + return; + } + + const normalizedMaxTokens = normalizePositiveInteger(modelConfigForm.maxTokens); + if (normalizedMaxTokens === null) { + toast.error(t('models.maxTokensPositiveError', { defaultValue: 'Max Tokens must be a positive integer' })); + return; + } + + // 如果模型名称为空,则默认为模型 ID + const dataToSave = { + ...modelConfigForm, + modelId: normalizedModelId, + maxTokens: normalizedMaxTokens, + modelName: normalizedModelName || normalizedModelId + }; + + axios + .post(`/api/projects/${projectId}/model-config`, dataToSave) + .then(response => { + if (selectedModelInfo && selectedModelInfo.id === response.data.id) { + setSelectedModelInfo(response.data); + } + toast.success(t('settings.saveSuccess')); + getModelConfigList(); + handleCloseModelDialog(); + }) + .catch(error => { + toast.error(t('settings.saveFailed')); + console.error(error); + }); + }; + + // 删除模型 + const handleDeleteModel = id => { + axios + .delete(`/api/projects/${projectId}/model-config/${id}`) + .then(response => { + toast.success(t('settings.deleteSuccess')); + getModelConfigList(); + }) + .catch(error => { + toast.error(t('settings.deleteFailed')); + }); + }; + + // 获取模型状态图标和颜色 + const getModelStatusInfo = model => { + const providerId = String(model?.providerId || '').toLowerCase(); + if (providerId === 'ollama') { + return { + icon: , + color: 'success', + text: t('models.localModel') + }; + } else if (model.apiKey) { + return { + icon: , + color: 'success', + text: t('models.apiKeyConfigured') + }; + } else { + return { + icon: , + color: 'warning', + text: t('models.apiKeyNotConfigured') + }; + } + }; + + const renderModelCard = model => { + const modelStatus = getModelStatusInfo(model); + const healthStatus = getHealthStatusInfo(model); + const providerId = String(model?.providerId || '').toLowerCase(); + const endpointLabel = `${formatEndpoint(model)}${ + providerId !== 'ollama' && !model.apiKey ? ' (' + t('models.unconfiguredAPIKey') + ')' : '' + }`; + + return ( + + + + { + e.target.src = '/imgs/models/default.svg'; + }} + /> + + + {model.modelName ? model.modelName : t('models.unselectedModel')} + + + {model.providerName} + + + + + + + + + + + + + + + + checkModelEndpointHealth(model)} + disabled={healthStatusMap[model.id]?.status === 'checking'} + > + + + + + + + + + + router.push(`/projects/${projectId}/playground?modelId=${model.id}`)} + color="secondary" + > + + + + + + handleOpenModelDialog(model)} color="primary"> + + + + + + handleDeleteModel(model.id)} + disabled={modelConfigList.length <= 1} + color="error" + > + + + + + + + ); + }; + + if (loading) { + return {t('textSplit.loading')}; + } + + return ( + + + + + {t('settings.modelConfig')} + + + + + + + + + + + + + {t('models.configuredModels', { defaultValue: 'Configured Models' })} + + + + + {configuredModelList.map(renderModelCard)} + {configuredModelList.length === 0 && ( + + {t('models.noConfiguredModels', { defaultValue: 'No configured models' })} + + )} + + + + + + + + + {t('models.unconfiguredModels', { defaultValue: 'Unconfigured Models' })} + + + + + {unconfiguredModelList.map(renderModelCard)} + {unconfiguredModelList.length === 0 && ( + + {t('models.noUnconfiguredModels', { defaultValue: 'No unconfigured models' })} + + )} + + + + + + {/* 模型表单对话框 */} + + {editingModel ? t('models.edit') : t('models.add')} + + + {/* provider */} + + + option.label} + value={ + providerOptions.find(p => p.id === modelConfigForm.providerId) || { + id: 'custom', + label: modelConfigForm.providerName || '' + } + } + onChange={onChangeProvider} + renderInput={params => ( + { + // 当用户手动输入时,更新 provider 字段 + setModelConfigForm(prev => ({ + ...prev, + providerId: 'custom', + providerName: e.target.value + })); + }} + /> + )} + renderOption={(props, option) => { + return ( +
+
+ { + e.target.src = '/imgs/models/default.svg'; + }} + /> + {option.label} +
+
+ ); + }} + /> +
+
+ {/* 接口地址 */} + + + + {/* API Key */} + + + + {/* 模型 ID */} + + + model && model.modelId) + .map(model => ({ + label: `${model.modelName} (${model.modelId})`, + modelName: model.modelName, + modelId: model.modelId, + providerId: model.providerId + }))} + value={modelConfigForm.modelId} + onChange={(event, newValue) => { + console.log('newValue', newValue); + const newId = newValue?.modelId || newValue || ''; + const newName = newValue?.modelName || newValue?.modelId || newValue || ''; + setModelConfigForm(prev => ({ + ...prev, + modelId: newId, + // 如果当前名称为空或与旧 ID 一致,则同步更新名称 + modelName: !prev.modelName || prev.modelName === prev.modelId ? newName : prev.modelName + })); + }} + renderInput={params => ( + { + setModelConfigForm(prev => ({ + ...prev, + modelId: e.target.value + })); + }} + /> + )} + /> + + + + {/* 模型名称 */} + + + + {/* 新增:视觉模型选择项 */} + + + {t('models.type')} + + + + + + {t('models.temperature')} + + + + + + {modelConfigForm.temperature} + + + + + + {t('models.maxTokens')} + + + + + + + + {t('models.maxTokensInputTip', { + defaultValue: `Slider range: 1-${MAX_GENERATION_TOKENS}. You can also input any positive integer.` + })} + + + + + {t('models.topP', { defaultValue: 'Top P' })} + + + + + + {modelConfigForm.topP} + + + +
+
+ + + + +
+
+ ); +} diff --git a/easy-dataset-main/components/settings/TaskSettings.js b/easy-dataset-main/components/settings/TaskSettings.js new file mode 100644 index 0000000..81bf4d1 --- /dev/null +++ b/easy-dataset-main/components/settings/TaskSettings.js @@ -0,0 +1,709 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { + Typography, + Box, + Button, + TextField, + Grid, + Card, + CardContent, + Slider, + InputAdornment, + Alert, + Snackbar, + FormControl, + Select, + InputLabel, + MenuItem, + Chip, + FormHelperText +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +import SaveIcon from '@mui/icons-material/Save'; +import useTaskSettings from '@/hooks/useTaskSettings'; + +export default function TaskSettings({ projectId }) { + const { t } = useTranslation(); + const { taskSettings, setTaskSettings, loading, error, success, setSuccess } = useTaskSettings(projectId); + + // 确保 multiTurnRounds 有正确的初始值 + useEffect(() => { + if ( + !loading && + taskSettings && + (taskSettings.multiTurnRounds === undefined || taskSettings.multiTurnRounds === null) + ) { + setTaskSettings(prev => ({ + ...prev, + multiTurnRounds: 3 // 默认值 + })); + } + }, [loading, taskSettings, setTaskSettings]); + + // 处理设置变更 + const handleSettingChange = e => { + const { name, value } = e.target; + setTaskSettings(prev => ({ + ...prev, + [name]: value + })); + }; + + // 处理滑块变更 + const handleSliderChange = name => (event, newValue) => { + setTaskSettings(prev => ({ + ...prev, + [name]: newValue + })); + }; + + // 保存任务配置 + const handleSaveTaskSettings = async () => { + try { + // 确保数组类型的数据被正确处理 + const settingsToSave = { ...taskSettings }; + + // 确保递归分块的分隔符数组存在 + if (settingsToSave.splitType === 'recursive' && settingsToSave.separatorsInput) { + if (!settingsToSave.separators || !Array.isArray(settingsToSave.separators)) { + settingsToSave.separators = settingsToSave.separatorsInput.split(',').map(item => item.trim()); + } + } + + console.log('Saving settings:', settingsToSave); + + const response = await fetch(`/api/projects/${projectId}/tasks`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(settingsToSave) + }); + + if (!response.ok) { + throw new Error(t('settings.saveTasksFailed')); + } + + setSuccess(true); + } catch (error) { + console.error('保存任务配置出错:', error); + //setError(error.message); + } + }; + + const handleCloseSnackbar = () => { + setSuccess(false); + //setError(null); + }; + + if (loading) { + return {t('common.loading')}; + } + + return ( + + {' '} + {/* 添加底部填充,为固定按钮留出空间 */} + + + + + + {t('settings.textSplitSettings')} + + + {/* 分块策略选择 */} + + {t('settings.splitType')} + + + + {/* Markdown模式设置 */} + {(!taskSettings.splitType || taskSettings.splitType === 'markdown') && ( + <> + + {t('settings.minLength')}: {taskSettings.textSplitMinLength} + + + + + {t('settings.maxLength')}: {taskSettings.textSplitMaxLength} + + + + )} + + {/* 通用 LangChain 参数设置 */} + {taskSettings.splitType && taskSettings.splitType !== 'markdown' && ( + <> + + {t('settings.chunkSize')}: {taskSettings.chunkSize || 3000} + + + + + {t('settings.chunkOverlap')}: {taskSettings.chunkOverlap || 200} + + + + )} + + {/* Text 分块器特殊设置 */} + {taskSettings.splitType === 'text' && ( + + )} + + {/* 自定义符号分块器特殊设置 */} + {taskSettings.splitType === 'custom' && ( + + )} + + {/* Code 分块器特殊设置 */} + {taskSettings.splitType === 'code' && ( + + {t('settings.codeLanguage')} + + {t('settings.codeLanguageHelper')} + + )} + + {/* Recursive 分块器特殊设置 */} + {taskSettings.splitType === 'recursive' && ( + + {t('settings.separators')} + ,-'} + onChange={e => { + const value = e.target.value; + // 同时更新输入框值和分隔符数组 + setTaskSettings(prev => ({ + ...prev, + separatorsInput: value, + separators: value.split(',').map(item => item.trim()) + })); + }} + helperText={t('settings.separatorsHelper')} + /> + + {(taskSettings.separators || ['|', '##', '>', '-']).map((sep, index) => ( + + ))} + + + )} + + + {t('settings.textSplitDescription')} + + + + + + + + + + + + {t('settings.questionGenSettings')} + + + + {t('settings.questionGenLength', { length: taskSettings.questionGenerationLength })} + + + + {t('settings.questionGenDescription')} + + + + {t('settings.questionMaskRemovingProbability', { + probability: taskSettings.questionMaskRemovingProbability + })} + + + + + + + + + + + + + + + {t('settings.pdfSettings')} + + + + + + + + + + + + + {/* 多轮对话数据集设置 */} + + + + + + {t('settings.multiTurnSettings')} + + + {/* 系统提示词 */} + + + {/* 对话场景 */} + + + {/* 对话轮数 */} + + {t('settings.multiTurnRounds', { rounds: taskSettings.multiTurnRounds || 3 })} + + + + {/* 角色A设定 */} + + + {/* 角色B设定 */} + + + + {t('settings.multiTurnDescription')} + + + + + + + {/* 测试集生成设置 */} + + + + + + {t('settings.evalQuestionSettings')} + + + {t('settings.evalQuestionSettingsDescription')} + + + + + { + const value = Math.max(0, parseInt(e.target.value) || 0); + setTaskSettings(prev => ({ + ...prev, + evalQuestionTypeRatios: { + ...prev.evalQuestionTypeRatios, + true_false: value + } + })); + }} + InputProps={{ inputProps: { min: 0 } }} + /> + + + {/* 单选题 */} + + { + const value = Math.max(0, parseInt(e.target.value) || 0); + setTaskSettings(prev => ({ + ...prev, + evalQuestionTypeRatios: { + ...prev.evalQuestionTypeRatios, + single_choice: value + } + })); + }} + InputProps={{ inputProps: { min: 0 } }} + /> + + + {/* 多选题 */} + + { + const value = Math.max(0, parseInt(e.target.value) || 0); + setTaskSettings(prev => ({ + ...prev, + evalQuestionTypeRatios: { + ...prev.evalQuestionTypeRatios, + multiple_choice: value + } + })); + }} + InputProps={{ inputProps: { min: 0 } }} + /> + + + {/* 固定短答案 */} + + { + const value = Math.max(0, parseInt(e.target.value) || 0); + setTaskSettings(prev => ({ + ...prev, + evalQuestionTypeRatios: { + ...prev.evalQuestionTypeRatios, + short_answer: value + } + })); + }} + InputProps={{ inputProps: { min: 0 } }} + /> + + + {/* 开放式回答 */} + + { + const value = Math.max(0, parseInt(e.target.value) || 0); + setTaskSettings(prev => ({ + ...prev, + evalQuestionTypeRatios: { + ...prev.evalQuestionTypeRatios, + open_ended: value + } + })); + }} + InputProps={{ inputProps: { min: 0 } }} + /> + + + + + {t('settings.evalQuestionRatioHelper')} + + + + + + + + + + + {t('settings.huggingfaceSettings')} + + + + + + + + + {t('settings.saveSuccess')} + + + + + {error} + + + {/* 吸底保存按钮 */} + + + + + ); +} diff --git a/easy-dataset-main/components/tasks/TaskActions.js b/easy-dataset-main/components/tasks/TaskActions.js new file mode 100644 index 0000000..f74cf27 --- /dev/null +++ b/easy-dataset-main/components/tasks/TaskActions.js @@ -0,0 +1,27 @@ +'use client'; + +import React from 'react'; +import { IconButton, Tooltip } from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import StopCircleIcon from '@mui/icons-material/StopCircle'; +import { useTranslation } from 'react-i18next'; + +// 任务操作组件 +export default function TaskActions({ task, onAbort, onDelete }) { + const { t } = useTranslation(); + + // 处理中的任务显示中断按钮,其他状态显示删除按钮 + return task.status === 0 ? ( + + onAbort(task.id)}> + + + + ) : ( + + onDelete(task.id)}> + + + + ); +} diff --git a/easy-dataset-main/components/tasks/TaskFilters.js b/easy-dataset-main/components/tasks/TaskFilters.js new file mode 100644 index 0000000..af94635 --- /dev/null +++ b/easy-dataset-main/components/tasks/TaskFilters.js @@ -0,0 +1,74 @@ +'use client'; + +import React from 'react'; +import { + Box, + FormControl, + InputLabel, + Select, + MenuItem, + OutlinedInput, + IconButton, + Tooltip, + CircularProgress +} from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; +import { useTranslation } from 'react-i18next'; + +export default function TaskFilters({ statusFilter, setStatusFilter, typeFilter, setTypeFilter, loading, onRefresh }) { + const { t } = useTranslation(); + + const taskTypeOptions = [ + 'text-processing', + 'file-processing', + 'pdf-processing', + 'question-generation', + 'answer-generation', + 'data-cleaning', + 'data-distillation', + 'eval-generation', + 'multi-turn-generation', + 'image-question-generation' + ]; + + return ( + + + {t('tasks.filters.status')} + + + + + {t('tasks.filters.type')} + + + + + + {loading ? : } + + + + ); +} diff --git a/easy-dataset-main/components/tasks/TaskProgress.js b/easy-dataset-main/components/tasks/TaskProgress.js new file mode 100644 index 0000000..2ac970d --- /dev/null +++ b/easy-dataset-main/components/tasks/TaskProgress.js @@ -0,0 +1,36 @@ +'use client'; + +import React from 'react'; +import { Stack, LinearProgress, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +// 任务进度组件 +export default function TaskProgress({ task }) { + const { t } = useTranslation(); + + // 如果没有总数,则不显示进度条 + if (task.totalCount === 0) return '-'; + + // 计算进度百分比 + const progress = (task.completedCount / task.totalCount) * 100; + + return ( + + + + {task.completedCount} / {task.totalCount} ({Math.round(progress)}%) + + + ); +} diff --git a/easy-dataset-main/components/tasks/TaskStatusChip.js b/easy-dataset-main/components/tasks/TaskStatusChip.js new file mode 100644 index 0000000..6f6111b --- /dev/null +++ b/easy-dataset-main/components/tasks/TaskStatusChip.js @@ -0,0 +1,48 @@ +'use client'; + +import React from 'react'; +import { Chip, CircularProgress, Box } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +// 任务状态显示组件 +export default function TaskStatusChip({ status }) { + const { t } = useTranslation(); + + // 状态映射配置 + const STATUS_CONFIG = { + 0: { + label: t('tasks.status.processing'), + color: 'warning', + loading: true + }, + 1: { + label: t('tasks.status.completed'), + color: 'success' + }, + 2: { + label: t('tasks.status.failed'), + color: 'error' + }, + 3: { + label: t('tasks.status.aborted'), + color: 'default' + } + }; + + const statusInfo = STATUS_CONFIG[status] || { + label: t('tasks.status.unknown'), + color: 'default' + }; + + // 处理中状态显示加载动画 + if (status === 0) { + return ( + + + + + ); + } + + return ; +} diff --git a/easy-dataset-main/components/tasks/TasksTable.js b/easy-dataset-main/components/tasks/TasksTable.js new file mode 100644 index 0000000..84eabbc --- /dev/null +++ b/easy-dataset-main/components/tasks/TasksTable.js @@ -0,0 +1,293 @@ +'use client'; + +import React from 'react'; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + Typography, + CircularProgress, + Box, + TablePagination, + Tooltip +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { formatDistanceToNow } from 'date-fns'; +import { zhCN, enUS } from 'date-fns/locale'; + +import TaskStatusChip from './TaskStatusChip'; +import TaskProgress from './TaskProgress'; +import TaskActions from './TaskActions'; + +export default function TasksTable({ + tasks, + loading, + handleAbortTask, + handleDeleteTask, + page, + rowsPerPage, + handleChangePage, + handleChangeRowsPerPage, + totalCount +}) { + const { t, i18n } = useTranslation(); + + const formatDate = dateString => { + if (!dateString) return '-'; + const date = new Date(dateString); + return formatDistanceToNow(date, { + addSuffix: true, + locale: i18n.language === 'zh-CN' ? zhCN : enUS + }); + }; + + const calculateDuration = (startTimeStr, endTimeStr) => { + if (!startTimeStr || !endTimeStr) return '-'; + + try { + const startTime = new Date(startTimeStr); + const endTime = new Date(endTimeStr); + const duration = endTime - startTime; + const seconds = Math.floor(duration / 1000); + + if (seconds < 60) { + return t('tasks.duration.seconds', { seconds }); + } + if (seconds < 3600) { + const minutes = Math.floor(seconds / 60); + const remainingSeconds = seconds % 60; + return t('tasks.duration.minutes', { minutes, seconds: remainingSeconds }); + } + + const hours = Math.floor(seconds / 3600); + const remainingMinutes = Math.floor((seconds % 3600) / 60); + return t('tasks.duration.hours', { hours, minutes: remainingMinutes }); + } catch (error) { + console.error('Failed to calculate duration:', error); + return '-'; + } + }; + + const parseModelInfo = modelInfoString => { + let modelInfo = ''; + try { + const parsedModel = JSON.parse(modelInfoString); + modelInfo = parsedModel.modelName || parsedModel.name || '-'; + } catch { + modelInfo = modelInfoString || '-'; + } + return modelInfo; + }; + + const toTaskTypeLabel = taskType => { + if (!taskType) return '-'; + return String(taskType) + .split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); + }; + + const getLocalizedTaskType = taskType => { + return t(`tasks.types.${taskType}`, { defaultValue: toTaskTypeLabel(taskType) }); + }; + + const parseJsonSafely = input => { + if (!input || typeof input !== 'string') return null; + try { + return JSON.parse(input); + } catch { + return null; + } + }; + + const formatTaskNote = task => { + const note = String(task?.note || '').trim(); + if (!note) return '-'; + + const noteJson = parseJsonSafely(note); + if (noteJson) { + if (Array.isArray(noteJson.chunkIds)) { + return t('tasks.notes.selectedChunks', { count: noteJson.chunkIds.length }); + } + if (Array.isArray(noteJson.fileList)) { + return t('tasks.notes.fileBatch', { + count: noteJson.fileList.length, + strategy: noteJson.strategy || '-' + }); + } + return t('tasks.notes.jsonParams'); + } + + if (note === 'No chunks require question generation' || note.startsWith('No chunks require question gen')) { + return t('tasks.notes.noChunksQuestion'); + } + if (note === 'No chunks require cleaning' || note.startsWith('No chunks require clean')) { + return t('tasks.notes.noChunksCleaning'); + } + if (note.startsWith('Processing failed:')) { + return t('tasks.notes.processingFailed', { + error: note.replace('Processing failed:', '').trim() + }); + } + + const summaryMatch = note.match(/Processed:\s*(\d+)\/(\d+),\s*succeeded:\s*(\d+),\s*failed:\s*(\d+)/i); + if (summaryMatch) { + const [, processed, total, succeeded, failed] = summaryMatch; + + const questionMatch = note.match(/questions generated:\s*(\d+)/i); + if (questionMatch) { + return t('tasks.notes.questionSummary', { + processed, + total, + succeeded, + failed, + generated: questionMatch[1] + }); + } + + const datasetMatch = note.match(/datasets generated:\s*(\d+)/i); + if (datasetMatch) { + return t('tasks.notes.datasetSummary', { + processed, + total, + succeeded, + failed, + generated: datasetMatch[1] + }); + } + + const cleaningMatch = note.match(/total original length:\s*(\d+),\s*total cleaned length:\s*(\d+)/i); + if (cleaningMatch) { + return t('tasks.notes.cleaningSummary', { + processed, + total, + succeeded, + failed, + original: cleaningMatch[1], + cleaned: cleaningMatch[2] + }); + } + + return t('tasks.notes.genericSummary', { + processed, + total, + succeeded, + failed + }); + } + + return note; + }; + + const truncateNote = (note, maxLength = 48) => { + if (!note) return '-'; + if (note.length <= maxLength) return note; + return `${note.substring(0, maxLength)}...`; + }; + + return ( + + + + + + {t('tasks.table.type')} + {t('tasks.table.status')} + {t('tasks.table.progress')} + {t('tasks.table.createTime')} + {t('tasks.table.duration')} + {t('tasks.table.model')} + {t('tasks.table.note')} + {t('tasks.table.actions')} + + + + {loading && tasks.length === 0 ? ( + + + + + + {t('tasks.loading')} + + + + + ) : tasks.length === 0 ? ( + + + {t('tasks.empty')} + + + ) : ( + tasks.map(task => { + const noteText = formatTaskNote(task); + return ( + + {getLocalizedTaskType(task.taskType)} + + + + + + + + {formatDate(task.createAt)} + {task.endTime ? calculateDuration(task.startTime, task.endTime) : '-'} + {parseModelInfo(task.modelInfo)} + + {noteText !== '-' ? ( + + + {truncateNote(noteText)} + + + ) : ( + '-' + )} + + + + + + ); + }) + )} + +
+
+ + {tasks.length > 0 && ( + { + const calculatedFrom = page * rowsPerPage + 1; + const calculatedTo = Math.min((page + 1) * rowsPerPage, count); + return t('datasets.pagination', { + from: calculatedFrom, + to: calculatedTo, + count + }); + }} + /> + )} +
+ ); +} diff --git a/easy-dataset-main/components/text-split/BatchEditChunkDialog.js b/easy-dataset-main/components/text-split/BatchEditChunkDialog.js new file mode 100644 index 0000000..a094ab6 --- /dev/null +++ b/easy-dataset-main/components/text-split/BatchEditChunkDialog.js @@ -0,0 +1,180 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + RadioGroup, + FormControlLabel, + Radio, + FormControl, + FormLabel, + Box, + Typography, + Alert, + CircularProgress +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +/** + * 批量编辑文本块对话框 + * @param {Object} props + * @param {boolean} props.open - 对话框是否打开 + * @param {Function} props.onClose - 关闭对话框的回调 + * @param {Function} props.onConfirm - 确认编辑的回调 + * @param {Array} props.selectedChunks - 选中的文本块ID数组 + * @param {number} props.totalChunks - 文本块总数 + * @param {boolean} props.loading - 是否正在处理 + */ +export default function BatchEditChunksDialog({ + open, + onClose, + onConfirm, + selectedChunks = [], + totalChunks = 0, + loading = false +}) { + const { t } = useTranslation(); + const [position, setPosition] = useState('start'); // 'start' 或 'end' + const [content, setContent] = useState(''); + const [error, setError] = useState(''); + + // 处理位置变更 + const handlePositionChange = event => { + setPosition(event.target.value); + }; + + // 处理内容变更 + const handleContentChange = event => { + setContent(event.target.value); + if (error) setError(''); + }; + + // 处理确认 + const handleConfirm = () => { + if (!content.trim()) { + setError(t('batchEdit.contentRequired')); + return; + } + + onConfirm({ + position, + content: content.trim(), + chunkIds: selectedChunks + }); + }; + + // 处理关闭 + const handleClose = () => { + if (!loading) { + setContent(''); + setError(''); + setPosition('start'); + onClose(); + } + }; + + return ( + + {t('batchEdit.title')} + + + + {/* 选择提示 */} + + + {selectedChunks.length === totalChunks + ? t('batchEdit.allChunksSelected', { count: totalChunks }) + : t('batchEdit.selectedChunks', { + selected: selectedChunks.length, + total: totalChunks + })} + + + + {/* 位置选择 */} + + + {t('batchEdit.position')} + + + } label={t('batchEdit.atBeginning')} /> + } label={t('batchEdit.atEnd')} /> + + + + {/* 内容输入 */} + + + {/* 预览示例 */} + {content.trim() && ( + + + {t('batchEdit.preview')}: + + + {position === 'start' ? ( + <> + {content} + {'\n\n[原始文本块内容...]'} + + ) : ( + <> + {'[原始文本块内容...]\n\n'} + {content} + + )} + + + )} + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/text-split/ChunkBatchDeleteDialog.js b/easy-dataset-main/components/text-split/ChunkBatchDeleteDialog.js new file mode 100644 index 0000000..68bbf07 --- /dev/null +++ b/easy-dataset-main/components/text-split/ChunkBatchDeleteDialog.js @@ -0,0 +1,45 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Button, + CircularProgress +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export default function ChunkBatchDeleteDialog({ open, onClose, onConfirm, loading, count }) { + const { t } = useTranslation(); + + return ( + + + {t('textSplit.batchDeleteChunksConfirmTitle', { defaultValue: '确认批量删除' })} + + + + {t('textSplit.batchDeleteChunksConfirmMessage', { + count, + defaultValue: `您确定要删除选中的 ${count} 个文本块吗?此操作不可恢复。` + })} + + + + + + + + ); +} diff --git a/easy-dataset-main/components/text-split/ChunkCard.js b/easy-dataset-main/components/text-split/ChunkCard.js new file mode 100644 index 0000000..3c9d0bc --- /dev/null +++ b/easy-dataset-main/components/text-split/ChunkCard.js @@ -0,0 +1,449 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { useState, useEffect } from 'react'; +import { + Box, + Typography, + IconButton, + Chip, + Checkbox, + Tooltip, + Card, + CardContent, + CardActions, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + TextField, + CircularProgress +} from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import VisibilityIcon from '@mui/icons-material/Visibility'; +import QuizIcon from '@mui/icons-material/Quiz'; +import EditIcon from '@mui/icons-material/Edit'; +import CleaningServicesIcon from '@mui/icons-material/CleaningServices'; +import AssignmentIcon from '@mui/icons-material/Assignment'; +import { useTheme } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; + +// 编辑文本块对话框组件 +const EditChunkDialog = ({ open, chunk, onClose, onSave }) => { + const [content, setContent] = useState(chunk?.content || ''); + const { t } = useTranslation(); + + // 当文本块变化时更新内容 + useEffect(() => { + if (chunk?.content) { + setContent(chunk.content); + } + }, [chunk]); + + const handleSave = () => { + onSave(content); + onClose(); + }; + + return ( + + {t('textSplit.editChunk', { chunkId: chunk?.name })} + + setContent(e.target.value)} + variant="outlined" + sx={{ mt: 1 }} + /> + + + + + + + ); +}; + +export default function ChunkCard({ + chunk, + selected, + onSelect, + onView, + onDelete, + onGenerateQuestions, + onDataCleaning, + onEdit, + onGenerateEvalQuestions, // 新增:生成测评题目的回调 + projectId, + selectedModel // 添加selectedModel参数 +}) { + const theme = useTheme(); + const { t } = useTranslation(); + const router = useRouter(); + const [editDialogOpen, setEditDialogOpen] = useState(false); + const [chunkForEdit, setChunkForEdit] = useState(null); + const [generatingQuestions, setGeneratingQuestions] = useState(false); + const [generatingEval, setGeneratingEval] = useState(false); + + // 获取文本预览 + const getTextPreview = (content, maxLength = 150) => { + if (!content) return ''; + return content.length > maxLength ? `${content.substring(0, maxLength)}...` : content; + }; + + // 检查是否有已生成的问题 + const hasQuestions = chunk.questions && chunk.questions.length > 0; + + // 处理编辑按钮点击 + const handleEditClick = async () => { + try { + // 显示加载状态 + console.log('正在获取文本块完整内容...'); + console.log('projectId:', projectId, 'chunkId:', chunk.id); + + // 先获取完整的文本块内容,使用从外部传入的 projectId + const response = await fetch(`/api/projects/${projectId}/chunks/${encodeURIComponent(chunk.id)}`); + + if (!response.ok) { + throw new Error(t('textSplit.fetchChunkFailed')); + } + + const data = await response.json(); + console.log('获取文本块完整内容成功:', data); + + // 先设置完整数据,再打开对话框(与 ChunkList.js 中的实现一致) + setChunkForEdit(data); + setEditDialogOpen(true); + } catch (error) { + console.error(t('textSplit.fetchChunkError'), error); + // 如果出错,使用原始预览数据 + alert(t('textSplit.fetchChunkError')); + } + }; + + // 处理保存编辑内容 + const handleSaveEdit = newContent => { + if (onEdit) { + onEdit(chunk.id, newContent); + } + }; + + // 处理生成单个问题 - 后台执行,不阻塞UI + const handleGenerateQuestionsClick = async () => { + setGeneratingQuestions(true); + try { + await onGenerateQuestions([chunk.id]); + } finally { + // Always release loading state, even when generation fails. + setTimeout(() => { + setGeneratingQuestions(false); + }, 500); + } + }; + + // 处理生成测评题目 + const handleGenerateEvalQuestionsClick = async () => { + if (!onGenerateEvalQuestions) return; + + setGeneratingEval(true); + try { + await onGenerateEvalQuestions(chunk.id); + } finally { + // 延迟关闭加载状态 + setTimeout(() => { + setGeneratingEval(false); + }, 500); + } + }; + + return ( + <> + + + + + + + + {chunk.name} + + + + + {chunk.Questions.length > 0 && ( + + {chunk.Questions.map((q, index) => ( + + {index + 1}. {q.question} + + ))} + + } + arrow + placement="top" + > + { + if (!projectId) return; + router.push(`/projects/${projectId}/questions`); + }} + /> + + )} + {chunk.EvalDatasets && chunk.EvalDatasets.length > 0 && ( + { + if (!projectId) return; + router.push(`/projects/${projectId}/eval-datasets`); + }} + /> + )} + + + + + {getTextPreview(chunk.content)} + + + + + + + + + + + + + + + + {generatingQuestions ? : } + + + + + + + + {generatingEval ? : } + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* 编辑文本块对话框 */} + { + setEditDialogOpen(false); + setChunkForEdit(null); + }} + onSave={handleSaveEdit} + /> + + ); +} diff --git a/easy-dataset-main/components/text-split/ChunkDeleteDialog.js b/easy-dataset-main/components/text-split/ChunkDeleteDialog.js new file mode 100644 index 0000000..434fd89 --- /dev/null +++ b/easy-dataset-main/components/text-split/ChunkDeleteDialog.js @@ -0,0 +1,27 @@ +'use client'; + +import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions, Button } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export default function ChunkDeleteDialog({ open, onClose, onConfirm }) { + const { t } = useTranslation(); + return ( + + {t('common.confirmDelete')}? + + {t('common.confirmDelete')}? + + + + + + + ); +} diff --git a/easy-dataset-main/components/text-split/ChunkFilterDialog.js b/easy-dataset-main/components/text-split/ChunkFilterDialog.js new file mode 100644 index 0000000..222c02a --- /dev/null +++ b/easy-dataset-main/components/text-split/ChunkFilterDialog.js @@ -0,0 +1,124 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + TextField, + Typography, + Slider, + FormControlLabel, + Checkbox +} from '@mui/material'; +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; + +export default function ChunkFilterDialog({ open, onClose, onApply, initialFilters = {} }) { + const { t } = useTranslation(); + const [contentKeyword, setContentKeyword] = useState(initialFilters.contentKeyword || ''); + const [sizeRange, setSizeRange] = useState(initialFilters.sizeRange || [0, 10000]); + const [hasQuestions, setHasQuestions] = useState(initialFilters.hasQuestions || null); + + // 重置筛选条件 + const handleReset = () => { + setContentKeyword(''); + setSizeRange([0, 10000]); + setHasQuestions(null); + }; + + // 应用筛选 + const handleApply = () => { + onApply({ + contentKeyword, + sizeRange, + hasQuestions + }); + onClose(); + }; + + // 处理大小范围变化 + const handleSizeRangeChange = (event, newValue) => { + setSizeRange(newValue); + }; + + return ( + + {t('datasets.moreFilters', { defaultValue: '更多筛选' })} + + {/* 文本块内容筛选 */} + + + {t('textSplit.contentKeyword', { defaultValue: '文本块内容' })} + + setContentKeyword(e.target.value)} + variant="outlined" + /> + + + {/* 字数范围筛选 */} + + + + {t('textSplit.characterRange', { defaultValue: '字数范围' })} + + + {sizeRange[0]} - {sizeRange[1]} + + + + + + {/* 是否有问题的筛选 */} + + + {t('textSplit.questionStatus', { defaultValue: '问题状态' })} + + + setHasQuestions(null)} />} + label={t('textSplit.allChunks', { defaultValue: '全部' })} + /> + setHasQuestions(true)} />} + label={t('textSplit.generatedQuestions2', { defaultValue: '已生成问题' })} + /> + setHasQuestions(false)} />} + label={t('textSplit.ungeneratedQuestions', { defaultValue: '未生成问题' })} + /> + + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/text-split/ChunkList.js b/easy-dataset-main/components/text-split/ChunkList.js new file mode 100644 index 0000000..cc30f82 --- /dev/null +++ b/easy-dataset-main/components/text-split/ChunkList.js @@ -0,0 +1,413 @@ +'use client'; + +import { useState, useEffect, useMemo } from 'react'; +import { Box, Paper, Typography, CircularProgress, Pagination, Grid } from '@mui/material'; +import ChunkListHeader from './ChunkListHeader'; +import ChunkCard from './ChunkCard'; +import ChunkViewDialog from './ChunkViewDialog'; +import ChunkDeleteDialog from './ChunkDeleteDialog'; +import BatchEditChunksDialog from './BatchEditChunkDialog'; +import ChunkBatchDeleteDialog from './ChunkBatchDeleteDialog'; +import { useTheme } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; + +/** + * Chunk list component + * @param {Object} props + * @param {string} props.projectId - Project ID + * @param {Array} props.chunks - Chunk array + * @param {Function} props.onDelete - Delete callback + * @param {Function} props.onEdit - Edit callback + * @param {Function} props.onGenerateQuestions - Generate questions callback + * @param {Function} props.onDataCleaning - Data cleaning callback + * @param {string} props.questionFilter - Question filter + * @param {Function} props.onQuestionFilterChange - Question filter change callback + * @param {Object} props.selectedModel - 閫変腑鐨勬ā鍨嬩俊鎭? + */ +export default function ChunkList({ + projectId, + chunks = [], + onDelete, + onEdit, + onGenerateQuestions, + onGenerateEvalQuestions, + onDataCleaning, + loading = false, + questionFilter, + setQuestionFilter, + selectedModel, + onChunksUpdate +}) { + const theme = useTheme(); + const [page, setPage] = useState(1); + const [selectedChunks, setSelectedChunks] = useState([]); + const [viewChunk, setViewChunk] = useState(null); + const [viewDialogOpen, setViewDialogOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [chunkToDelete, setChunkToDelete] = useState(null); + const [batchEditDialogOpen, setBatchEditDialogOpen] = useState(false); + const [batchEditLoading, setBatchEditLoading] = useState(false); + const [batchDeleteDialogOpen, setBatchDeleteDialogOpen] = useState(false); + const [batchDeleteLoading, setBatchDeleteLoading] = useState(false); + + // 娣诲姞楂樼骇绛涢€夌姸鎬? + const [advancedFilters, setAdvancedFilters] = useState({ + contentKeyword: '', + sizeRange: [0, 10000], + hasQuestions: null + }); + + // 璁$畻娲昏穬绛涢€夋潯浠舵暟 + const activeFilterCount = useMemo(() => { + let count = 0; + if (advancedFilters.contentKeyword) count++; + if (advancedFilters.sizeRange[0] > 0 || advancedFilters.sizeRange[1] < 10000) count++; + if (advancedFilters.hasQuestions !== null) count++; + return count; + }, [advancedFilters]); + + const sortedChunks = useMemo( + () => + [...chunks].sort((a, b) => { + if (a.fileId !== b.fileId) { + return a.fileId.localeCompare(b.fileId); + } + + const getPartNumber = name => { + const match = name.match(/part-(\d+)/); + return match ? parseInt(match[1], 10) : 0; + }; + + const numA = getPartNumber(a.name); + const numB = getPartNumber(b.name); + + return numA - numB; + }), + [chunks] + ); + + const filteredChunks = useMemo(() => { + return sortedChunks.filter(chunk => { + if (advancedFilters.contentKeyword) { + const keyword = advancedFilters.contentKeyword.toLowerCase(); + if (!chunk.content?.toLowerCase().includes(keyword)) { + return false; + } + } + + const size = chunk.size || 0; + if (size < advancedFilters.sizeRange[0] || size > advancedFilters.sizeRange[1]) { + return false; + } + + if (advancedFilters.hasQuestions !== null) { + const hasQuestions = chunk.Questions && chunk.Questions.length > 0; + if (advancedFilters.hasQuestions !== hasQuestions) { + return false; + } + } + + return true; + }); + }, [sortedChunks, advancedFilters]); + + // 褰撶瓫閫夋潯浠跺彉鍖栨椂锛屾竻闄や笉鍦ㄧ瓫閫夌粨鏋滀腑鐨勯€変腑椤? + useEffect(() => { + const filteredChunkIds = filteredChunks.map(chunk => chunk.id); + setSelectedChunks(prev => prev.filter(id => filteredChunkIds.includes(id))); + }, [filteredChunks]); + + const itemsPerPage = 5; + const displayedChunks = useMemo(() => { + const startIndex = (page - 1) * itemsPerPage; + return filteredChunks.slice(startIndex, startIndex + itemsPerPage); + }, [filteredChunks, page]); + const totalPages = useMemo(() => Math.ceil(filteredChunks.length / itemsPerPage), [filteredChunks.length]); + const { t } = useTranslation(); + + const handlePageChange = (event, value) => { + setPage(value); + }; + + const handleViewChunk = async chunkId => { + try { + const response = await fetch(`/api/projects/${projectId}/chunks/${chunkId}`); + if (!response.ok) { + throw new Error(t('textSplit.fetchChunksFailed')); + } + + const data = await response.json(); + setViewChunk(data); + setViewDialogOpen(true); + } catch (error) { + console.error(t('textSplit.fetchChunksError'), error); + } + }; + + const handleCloseViewDialog = () => { + setViewDialogOpen(false); + }; + + const handleOpenDeleteDialog = chunkId => { + setChunkToDelete(chunkId); + setDeleteDialogOpen(true); + }; + + const handleCloseDeleteDialog = () => { + setDeleteDialogOpen(false); + setChunkToDelete(null); + }; + + const handleConfirmDelete = () => { + if (chunkToDelete && onDelete) { + onDelete(chunkToDelete); + } + handleCloseDeleteDialog(); + }; + + // 澶勭悊缂栬緫鏂囨湰鍧? + const handleEditChunk = async (chunkId, newContent) => { + if (onEdit) { + onEdit(chunkId, newContent); + onChunksUpdate(); + } + }; + + // 澶勭悊閫夋嫨鏂囨湰鍧? + const handleSelectChunk = chunkId => { + setSelectedChunks(prev => { + if (prev.includes(chunkId)) { + return prev.filter(id => id !== chunkId); + } else { + return [...prev, chunkId]; + } + }); + }; + + const handleSelectAll = () => { + if (selectedChunks.length === filteredChunks.length) { + setSelectedChunks([]); + } else { + setSelectedChunks(filteredChunks.map(chunk => chunk.id)); + } + }; + + const handleBatchGenerateQuestions = () => { + if (onGenerateQuestions && selectedChunks.length > 0) { + onGenerateQuestions(selectedChunks); + } + }; + + const handleBatchEdit = async editData => { + try { + setBatchEditLoading(true); + + // 璋冪敤鎵归噺缂栬緫API + const response = await fetch(`/api/projects/${projectId}/chunks/batch-edit`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + position: editData.position, + content: editData.content, + chunkIds: editData.chunkIds + }) + }); + + if (!response.ok) { + throw new Error('鎵归噺缂栬緫澶辫触'); + } + + const result = await response.json(); + + if (result.success) { + // 缂栬緫鎴愬姛鍚庯紝鍒锋柊鏂囨湰鍧楁暟鎹? + if (onChunksUpdate) { + onChunksUpdate(); + } + + // 娓呯┖閫変腑鐘舵€? + setSelectedChunks([]); + + // 鍏抽棴瀵硅瘽妗? + setBatchEditDialogOpen(false); + + // 鏄剧ず鎴愬姛娑堟伅 + console.log(`鎴愬姛鏇存柊浜?${result.updatedCount} 涓枃鏈潡`); + } else { + throw new Error(result.message || '鎵归噺缂栬緫澶辫触'); + } + } catch (error) { + console.error('鎵归噺缂栬緫澶辫触:', error); + // 杩欓噷鍙互娣诲姞閿欒鎻愮ず + } finally { + setBatchEditLoading(false); + } + }; + + // 鎵撳紑鎵归噺缂栬緫瀵硅瘽妗? + const handleOpenBatchEdit = () => { + setBatchEditDialogOpen(true); + }; + + // 鍏抽棴鎵归噺缂栬緫瀵硅瘽妗? + const handleCloseBatchEdit = () => { + setBatchEditDialogOpen(false); + }; + + if (loading) { + return ( + + + + ); + } + + // 澶勭悊绛涢€夊彉鍖? + const handleFilterChange = filters => { + setAdvancedFilters(filters); + setPage(1); // 閲嶇疆鍒扮涓€椤? + }; + + // 鎵撳紑鎵归噺鍒犻櫎瀵硅瘽妗? + const handleOpenBatchDelete = () => { + setBatchDeleteDialogOpen(true); + }; + + // 鍏抽棴鎵归噺鍒犻櫎瀵硅瘽妗? + const handleCloseBatchDelete = () => { + setBatchDeleteDialogOpen(false); + }; + + // 纭鎵归噺鍒犻櫎 + const handleConfirmBatchDelete = async () => { + if (selectedChunks.length === 0) return; + + try { + setBatchDeleteLoading(true); + + let successCount = 0; + let failCount = 0; + + // 寰幆璋冪敤鍗曚釜鍒犻櫎鎺ュ彛 + for (const chunkId of selectedChunks) { + try { + await onDelete(chunkId); + successCount++; + } catch (error) { + console.error(`鍒犻櫎鏂囨湰鍧?${chunkId} 澶辫触:`, error); + failCount++; + } + } + + // 鏄剧ず鍒犻櫎缁撴灉 + if (failCount === 0) { + console.log(`鎴愬姛鍒犻櫎 ${successCount} 涓枃鏈潡`); + } else { + console.log(`删除完成:成功 ${successCount} 个,失败 ${failCount} 个`); + } + + // 娓呯┖閫変腑鐘舵€? + setSelectedChunks([]); + + // 鍒锋柊鏁版嵁 + if (onChunksUpdate) { + onChunksUpdate(); + } + + // 鍏抽棴瀵硅瘽妗? + setBatchDeleteDialogOpen(false); + } catch (error) { + console.error('鎵归噺鍒犻櫎澶辫触:', error); + } finally { + setBatchDeleteLoading(false); + } + }; + + return ( + + setQuestionFilter(event.target.value)} + chunks={chunks} + selectedModel={selectedModel} + onFilterChange={handleFilterChange} + activeFilterCount={activeFilterCount} + /> + + + {displayedChunks.map(chunk => ( + + handleSelectChunk(chunk.id)} + onView={() => handleViewChunk(chunk.id)} + onDelete={() => handleOpenDeleteDialog(chunk.id)} + onEdit={handleEditChunk} + onGenerateQuestions={() => onGenerateQuestions && onGenerateQuestions([chunk.id])} + onGenerateEvalQuestions={() => onGenerateEvalQuestions && onGenerateEvalQuestions(chunk.id)} + onDataCleaning={() => onDataCleaning && onDataCleaning([chunk.id])} + projectId={projectId} + selectedModel={selectedModel} + /> + + ))} + + + {chunks.length === 0 && ( + + + {t('textSplit.noChunks')} + + + )} + + {totalPages > 1 && ( + + + + )} + + {/* 鏂囨湰鍧楄鎯呭璇濇 */} + + + {/* 鍒犻櫎纭瀵硅瘽妗?*/} + + + {/* 鎵归噺缂栬緫瀵硅瘽妗?*/} + + + {/* 鎵归噺鍒犻櫎纭瀵硅瘽妗?*/} + + + ); +} diff --git a/easy-dataset-main/components/text-split/ChunkListHeader.js b/easy-dataset-main/components/text-split/ChunkListHeader.js new file mode 100644 index 0000000..89e9fb7 --- /dev/null +++ b/easy-dataset-main/components/text-split/ChunkListHeader.js @@ -0,0 +1,400 @@ +'use client'; + +import { Box, Typography, Checkbox, Button, Select, MenuItem, Tooltip, Menu, IconButton, Badge } from '@mui/material'; +import QuizIcon from '@mui/icons-material/Quiz'; +import DownloadIcon from '@mui/icons-material/Download'; +import AutoFixHighIcon from '@mui/icons-material/AutoFixHigh'; +import CleaningServicesIcon from '@mui/icons-material/CleaningServices'; +import AssessmentIcon from '@mui/icons-material/Assessment'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import FilterListIcon from '@mui/icons-material/FilterList'; +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; +import axios from 'axios'; +import { toast } from 'sonner'; +import { useTranslation } from 'react-i18next'; +import { useState } from 'react'; +import ChunkFilterDialog from './ChunkFilterDialog'; + +export default function ChunkListHeader({ + projectId, + totalChunks, + selectedChunks, + onSelectAll, + onBatchGenerateQuestions, + onBatchEditChunks, + onBatchDeleteChunks, + questionFilter, + setQuestionFilter, + chunks = [], // 添加chunks参数,用于导出文本块 + selectedModel = {}, + onFilterChange = null, + activeFilterCount = 0 +}) { + const { t, i18n } = useTranslation(); + + // 添加更多菜单的状态和锚点 + const [moreMenuAnchorEl, setMoreMenuAnchorEl] = useState(null); + const isMoreMenuOpen = Boolean(moreMenuAnchorEl); + + // 添加筛选对话框状态 + const [filterDialogOpen, setFilterDialogOpen] = useState(false); + + // 自动任务菜单状态 + const [autoTasksMenuAnchorEl, setAutoTasksMenuAnchorEl] = useState(null); + const isAutoTasksMenuOpen = Boolean(autoTasksMenuAnchorEl); + + const handleAutoTasksClick = event => { + setAutoTasksMenuAnchorEl(event.currentTarget); + }; + + const handleAutoTasksClose = () => { + setAutoTasksMenuAnchorEl(null); + }; + + // 打开更多菜单 + const handleMoreMenuClick = event => { + setMoreMenuAnchorEl(event.currentTarget); + }; + + // 关闭更多菜单 + const handleMoreMenuClose = () => { + setMoreMenuAnchorEl(null); + }; + + // 处理批量编辑,关闭菜单并调用原有函数 + const handleBatchEdit = () => { + handleMoreMenuClose(); + onBatchEditChunks(); + }; + + // 处理批量删除,关闭菜单并调用原有函数 + const handleBatchDelete = () => { + handleMoreMenuClose(); + onBatchDeleteChunks(); + }; + + // 处理导出文本块,关闭菜单并调用原有函数 + const handleExport = () => { + handleMoreMenuClose(); + handleExportChunks(); + }; + + // 创建自动提取问题任务 + const handleCreateAutoQuestionTask = async () => { + if (!projectId || !selectedModel?.id) { + toast.error(t('textSplit.selectModelFirst', { defaultValue: '请先选择模型' })); + return; + } + + try { + // 调用创建任务接口 + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'question-generation', + modelInfo: selectedModel, + language: i18n.language, + detail: '批量生成问题任务' + }); + + if (response.data?.code === 0) { + toast.success(t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理未生成问题的文本块' })); + } else { + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + response.data?.message); + } + } catch (error) { + console.error('创建自动提取问题任务失败:', error); + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message); + } + }; + + // 创建自动数据清洗任务 + const handleCreateAutoDataCleaningTask = async () => { + if (!projectId || !selectedModel?.id) { + toast.error(t('textSplit.selectModelFirst', { defaultValue: '请先选择模型' })); + return; + } + + try { + // 调用创建任务接口 + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'data-cleaning', + modelInfo: selectedModel, + language: i18n.language, + detail: '批量数据清洗任务' + }); + + if (response.data?.code === 0) { + toast.success( + t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理所有文本块进行数据清洗' }) + ); + } else { + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + response.data?.message); + } + } catch (error) { + console.error('创建自动数据清洗任务失败:', error); + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message); + } + }; + + // 创建自动生成评估数据集任务 + const handleCreateAutoEvalGenerationTask = async () => { + if (!projectId || !selectedModel?.id) { + toast.error(t('textSplit.selectModelFirst', { defaultValue: '请先选择模型' })); + return; + } + + try { + // 调用创建任务接口 + const response = await axios.post(`/api/projects/${projectId}/tasks`, { + taskType: 'eval-generation', + modelInfo: selectedModel, + language: i18n.language, + detail: '批量生成评估数据集任务' + }); + + if (response.data?.code === 0) { + toast.success( + t('tasks.createSuccess', { + defaultValue: '后台任务已创建,系统将自动为所有未生成评估题目的文本块生成评估数据集' + }) + ); + } else { + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + response.data?.message); + } + } catch (error) { + console.error('创建自动生成评估数据集任务失败:', error); + toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message); + } + }; + + // 导出文本块为JSON文件的函数 + const handleExportChunks = () => { + if (!chunks || chunks.length === 0) return; + + // 创建要导出的数据对象 + const exportData = chunks.map(chunk => ({ + name: chunk.name, + projectId: chunk.projectId, + fileName: chunk.fileName, + content: chunk.content, + summary: chunk.summary, + size: chunk.size + })); + + // 将数据转换为JSON字符串 + const jsonString = JSON.stringify(exportData, null, 2); + + // 创建Blob对象 + const blob = new Blob([jsonString], { type: 'application/json' }); + + // 创建下载链接 + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `text-chunks-export-${new Date().toISOString().split('T')[0]}.json`; + + // 触发下载 + document.body.appendChild(a); + a.click(); + + // 清理 + document.body.removeChild(a); + URL.revokeObjectURL(url); + }; + + return ( + + + 0 && selectedChunks.length < totalChunks} + onChange={onSelectAll} + /> + {t('textSplit.selectedCount', { count: selectedChunks.length })} + + + + {/* 更多筛选按钮 */} + + + + + + + + + + {/* 自动任务下拉菜单 */} + + + + + { + handleCreateAutoQuestionTask(); + handleAutoTasksClose(); + }} + > + + {t('textSplit.autoGenerateQuestions')} + + + + + { + handleCreateAutoEvalGenerationTask(); + handleAutoTasksClose(); + }} + > + + {t('textSplit.autoEvalGeneration', { defaultValue: '自动生成评估集' })} + + + + + { + handleCreateAutoDataCleaningTask(); + handleAutoTasksClose(); + }} + > + + {t('textSplit.autoDataCleaning', { defaultValue: '自动数据清洗' })} + + + + + {/* 更多菜单按钮 */} + + + + + + + {/* 更多操作下拉菜单 */} + + + + {t('batchEdit.batchEdit', { defaultValue: '批量编辑' })} + + + + {t('textSplit.batchDeleteChunks', { defaultValue: '批量删除' })} + + + + {t('textSplit.exportChunks', { defaultValue: '导出文本块' })} + + + + + + {/* 筛选对话框 */} + setFilterDialogOpen(false)} onApply={onFilterChange} /> + + ); +} diff --git a/easy-dataset-main/components/text-split/ChunkViewDialog.js b/easy-dataset-main/components/text-split/ChunkViewDialog.js new file mode 100644 index 0000000..9f24a2f --- /dev/null +++ b/easy-dataset-main/components/text-split/ChunkViewDialog.js @@ -0,0 +1,31 @@ +'use client'; + +import { Box, Button, Dialog, DialogTitle, DialogContent, DialogActions, CircularProgress } from '@mui/material'; +import ReactMarkdown from 'react-markdown'; +import { useTranslation } from 'react-i18next'; +import 'github-markdown-css/github-markdown-light.css'; + +export default function ChunkViewDialog({ open, chunk, onClose }) { + const { t } = useTranslation(); + return ( + + {t('textSplit.chunkDetails', { chunkId: chunk?.name })} + + {chunk ? ( + +
+ {chunk.content} +
+
+ ) : ( + + + + )} +
+ + + +
+ ); +} diff --git a/easy-dataset-main/components/text-split/DomainAnalysis.js b/easy-dataset-main/components/text-split/DomainAnalysis.js new file mode 100644 index 0000000..98150d5 --- /dev/null +++ b/easy-dataset-main/components/text-split/DomainAnalysis.js @@ -0,0 +1,560 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Box, + Paper, + Typography, + Divider, + CircularProgress, + Tabs, + Tab, + List, + ListItem, + ListItemText, + Collapse, + IconButton, + TextField, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + Tooltip, + Menu, + MenuItem +} from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import TabPanel from './components/TabPanel'; +import ReactMarkdown from 'react-markdown'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; +import AddIcon from '@mui/icons-material/Add'; +import EditIcon from '@mui/icons-material/Edit'; +import DeleteIcon from '@mui/icons-material/Delete'; +import MoreVertIcon from '@mui/icons-material/MoreVert'; +import axios from 'axios'; +import { toast } from 'sonner'; + +import 'github-markdown-css/github-markdown-light.css'; + +/** + * 领域分析组件 + * @param {Object} props + * @param {string} props.projectId - 项目ID + * @param {Array} props.toc - 目录结构数组 + * @param {Array} props.tags - 标签树数组 + * @param {boolean} props.loading - 是否加载中 + * @param {Function} props.onTagsUpdate - 标签更新回调 + */ + +// 领域树节点组件 +function TreeNode({ node, level = 0, onEdit, onDelete, onAddChild }) { + const [open, setOpen] = useState(true); + const theme = useTheme(); + const hasChildren = node.child && node.child.length > 0; + const [anchorEl, setAnchorEl] = useState(null); + const menuOpen = Boolean(anchorEl); + const { t } = useTranslation(); + + const handleClick = () => { + if (hasChildren) { + setOpen(!open); + } + }; + + const handleMenuOpen = event => { + event.stopPropagation(); + setAnchorEl(event.currentTarget); + }; + + const handleMenuClose = event => { + if (event) event.stopPropagation(); + setAnchorEl(null); + }; + + const handleEdit = event => { + event.stopPropagation(); + onEdit(node); + handleMenuClose(); + }; + + const handleDelete = event => { + event.stopPropagation(); + onDelete(node); + handleMenuClose(); + }; + + const handleAddChild = event => { + event.stopPropagation(); + onAddChild(node); + handleMenuClose(); + }; + + return ( + <> + + + + + + + {hasChildren && (open ? : )} + + + e.stopPropagation()}> + + + {t('textSplit.editTag')} + + + + {t('textSplit.deleteTag')} + + {level === 0 && ( + + + {t('textSplit.addTag')} + + )} + + + + {hasChildren && ( + + + {node.child.map((childNode, index) => ( + + ))} + + + )} + + ); +} + +// 领域树组件 +function DomainTree({ tags, onEdit, onDelete, onAddChild }) { + return ( + + {tags.map((node, index) => ( + + ))} + + ); +} + +export default function DomainAnalysis({ projectId, toc = '', loading = false }) { + const theme = useTheme(); + const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState(0); + const [dialogOpen, setDialogOpen] = useState(false); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [currentNode, setCurrentNode] = useState(null); + const [parentNode, setParentNode] = useState(''); + const [dialogMode, setDialogMode] = useState('add'); + const [labelValue, setLabelValue] = useState({}); + const [saving, setSaving] = useState(false); + const [tags, setTags] = useState([]); + const [snackbar, setSnackbar] = useState({ + open: false, + message: '', + severity: 'success' + }); + + const handleCloseSnackbar = () => { + setSnackbar(prev => ({ ...prev, open: false })); + }; + + useEffect(() => { + getTags(); + }, []); + const getTags = async () => { + const response = await axios.get(`/api/projects/${projectId}/tags`); + setTags(response.data.tags); + }; + // 处理标签切换 + const handleTabChange = (event, newValue) => { + setActiveTab(newValue); + }; + + // 打开添加标签对话框 + const handleAddTag = () => { + setDialogMode('add'); + setCurrentNode(null); + setParentNode(null); + setLabelValue({}); + setDialogOpen(true); + }; + + // 打开编辑标签对话框 + const handleEditTag = node => { + setDialogMode('edit'); + setCurrentNode({ id: node.id, label: node.label }); + setLabelValue({ id: node.id, label: node.label }); + setDialogOpen(true); + }; + + // 打开添加子标签对话框 + const handleAddChildTag = parentNode => { + setDialogMode('addChild'); + setParentNode(parentNode.label); + setLabelValue({ parentId: parentNode.id }); + setDialogOpen(true); + }; + + // 打开删除标签对话框 + const handleDeleteTag = node => { + setCurrentNode(node); + setDeleteDialogOpen(true); + }; + + // 关闭对话框 + const handleCloseDialog = () => { + setDialogOpen(false); + setDeleteDialogOpen(false); + }; + + // 查找并更新节点 + const findAndUpdateNode = (nodes, targetNode, newLabel) => { + return nodes.map(node => { + if (node === targetNode) { + return { ...node, label: newLabel }; + } + if (node.child && node.child.length > 0) { + return { ...node, child: findAndUpdateNode(node.child, targetNode, newLabel) }; + } + return node; + }); + }; + + // 查找并删除节点 + const findAndDeleteNode = (nodes, targetNode) => { + return nodes + .filter(node => node !== targetNode) + .map(node => { + if (node.child && node.child.length > 0) { + return { ...node, child: findAndDeleteNode(node.child, targetNode) }; + } + return node; + }); + }; + + // 查找并添加子节点 + const findAndAddChildNode = (nodes, parentNode, childLabel) => { + return nodes.map(node => { + if (node === parentNode) { + const childArray = node.child || []; + return { + ...node, + child: [...childArray, { label: childLabel, child: [] }] + }; + } + if (node.child && node.child.length > 0) { + return { ...node, child: findAndAddChildNode(node.child, parentNode, childLabel) }; + } + return node; + }); + }; + + // 保存标签更改 + const saveTagChanges = async updatedTags => { + console.log('保存标签更改:', updatedTags); + setSaving(true); + try { + const response = await fetch(`/api/projects/${projectId}/tags`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ tags: updatedTags }) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('domain.errors.saveFailed')); + } + getTags(); + setSnackbar({ + open: true, + message: t('domain.messages.updateSuccess'), + severity: 'success' + }); + } catch (error) { + console.error('保存标签失败:', error); + setSnackbar({ + open: true, + message: error.message || '保存标签失败', + severity: 'error' + }); + } finally { + setSaving(false); + } + }; + + // 提交表单 + const handleSubmit = async () => { + if (!labelValue.label.trim()) { + setSnackbar({ + open: true, + message: '标签名称不能为空', + severity: 'error' + }); + return; + } + + await saveTagChanges(labelValue); + handleCloseDialog(); + }; + + const handleConfirmDelete = async () => { + if (!currentNode) return; + + const res = await axios.delete(`/api/projects/${projectId}/tags?id=${currentNode.id}`); + if (res.status === 200) { + toast.success('删除成功'); + getTags(); + } + + setDeleteDialogOpen(false); + }; + + if (loading) { + return ( + + + + ); + } + + if (toc.length === 0) { + return ( + + + {t('domain.noToc')} + + + ); + } + + return ( + + + + + + + + + + + + {t('domain.tabs.tree')} + + + + + + + {tags && tags.length > 0 ? ( + + ) : ( + + + {t('domain.noTags')} + + + + )} + + + + + + + {t('domain.docStructure')} + + + +
+ ( +
+ {children} +
+ ) + }} + > + {toc} +
+
+
+
+
+
+
+ + {/* 添加/编辑标签对话框 */} + + + {dialogMode === 'add' + ? t('domain.dialog.addTitle') + : dialogMode === 'edit' + ? t('domain.dialog.editTitle') + : t('domain.dialog.addChildTitle')} + + + + {dialogMode === 'add' + ? t('domain.dialog.inputRoot') + : dialogMode === 'edit' + ? t('domain.dialog.inputEdit') + : t('domain.dialog.inputChild', { label: parentNode })} + + setLabelValue({ ...labelValue, label: e.target.value })} + /> + + + + + + + + {/* 删除确认对话框 */} + + {t('common.confirmDelete')} + + + {t('domain.dialog.deleteConfirm', { label: currentNode?.label })} + {currentNode?.child && currentNode.child.length > 0 && t('domain.dialog.deleteWarning')} + + + + + + + +
+ ); +} diff --git a/easy-dataset-main/components/text-split/FileUploader.js b/easy-dataset-main/components/text-split/FileUploader.js new file mode 100644 index 0000000..380588e --- /dev/null +++ b/easy-dataset-main/components/text-split/FileUploader.js @@ -0,0 +1,360 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Paper, Grid } from '@mui/material'; +import { useTheme } from '@mui/material/styles'; +import { useAtomValue } from 'jotai/index'; +import { selectedModelInfoAtom } from '@/lib/store'; +import UploadArea from './components/UploadArea'; +import FileList from './components/FileList'; +import DeleteConfirmDialog from './components/DeleteConfirmDialog'; +import PdfProcessingDialog from './components/PdfProcessingDialog'; +import DomainTreeActionDialog from './components/DomainTreeActionDialog'; +import FileLoadingProgress from './components/FileLoadingProgress'; +import { fileApi, taskApi } from '@/lib/api'; +import { getContent, checkMaxSize, checkInvalidFiles, getvalidFiles } from '@/lib/file/file-process'; +import { toast } from 'sonner'; + +export default function FileUploader({ + projectId, + onUploadSuccess, + onFileDeleted, + sendToPages, + setPdfStrategy, + pdfStrategy, + selectedViosnModel, + setSelectedViosnModel, + setPageLoading, + taskFileProcessing, + fileTask +}) { + const theme = useTheme(); + const { t } = useTranslation(); + const [files, setFiles] = useState([]); + const [pdfFiles, setPdfFiles] = useState([]); + const [uploadedFiles, setUploadedFiles] = useState({}); + const selectedModelInfo = useAtomValue(selectedModelInfoAtom); + const [uploading, setUploading] = useState(false); + const [loading, setLoading] = useState(false); + const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false); + const [pdfProcessConfirmOpen, setpdfProcessConfirmOpen] = useState(false); + const [fileToDelete, setFileToDelete] = useState({}); + const [domainTreeActionOpen, setDomainTreeActionOpen] = useState(false); + const [domainTreeAction, setDomainTreeAction] = useState(''); + const [isFirstUpload, setIsFirstUpload] = useState(false); + const [pendingAction, setPendingAction] = useState(null); + const [taskSettings, setTaskSettings] = useState(null); + const [visionModels, setVisionModels] = useState([]); + const [currentPage, setCurrentPage] = useState(1); + const [pageSize] = useState(10); + const [searchFileName, setSearchFileName] = useState(''); + + useEffect(() => { + fetchUploadedFiles(); + }, [currentPage, searchFileName]); + + /** + * 处理 PDF 处理方式选择 + */ + const handleRadioChange = event => { + const modelId = event.target.selectedVision; + setPdfStrategy(event.target.value); + + if (event.target.value === 'mineru') { + toast.success(t('textSplit.mineruSelected')); + } else if (event.target.value === 'mineru-local') { + toast.success(t('textSplit.mineruLocalSelected')); + } else if (event.target.value === 'vision') { + const model = visionModels.find(item => item.id === modelId); + toast.success( + t('textSplit.customVisionModelSelected', { + name: model.modelName, + provider: model.projectName + }) + ); + } else { + toast.success(t('textSplit.defaultSelected')); + } + }; + + /** + * 获取上传的文件列表 + * @param {*} page + * @param {*} size + * @param {*} fileName + */ + const fetchUploadedFiles = async (page = currentPage, size = pageSize, fileName = searchFileName) => { + try { + setLoading(true); + const data = await fileApi.getFiles({ projectId, page, size, fileName, t }); + setUploadedFiles(data); + + setIsFirstUpload(data.total === 0); + + const taskData = await taskApi.getProjectTasks(projectId); + setTaskSettings(taskData); + + //使用Jotai会出现数据获取的延迟,导致这里模型获取不到,改用localStorage获取模型信息 + const model = JSON.parse(localStorage.getItem('modelConfigList')); + + //过滤出视觉模型 + const visionItems = model.filter(item => item.type === 'vision'); + + //先默认选择第一个配置的视觉模型 + if (visionItems.length > 0) { + setSelectedViosnModel(visionItems[0].id); + } + + setVisionModels(visionItems); + } catch (error) { + toast.error(error.message); + } finally { + setLoading(false); + } + }; + + /** + * 处理文件选择 + */ + const handleFileSelect = event => { + const selectedFiles = Array.from(event.target.files); + + checkMaxSize(selectedFiles); + checkInvalidFiles(selectedFiles); + + const validFiles = getvalidFiles(selectedFiles); + + if (validFiles.length > 0) { + setFiles(prev => [...prev, ...validFiles]); + } + const hasPdfFiles = selectedFiles.filter(file => file.name.endsWith('.pdf')); + if (hasPdfFiles.length > 0) { + setpdfProcessConfirmOpen(true); + setPdfFiles(hasPdfFiles); + } + }; + + /** + * 从待上传文件列表中移除文件 + */ + const removeFile = index => { + const fileToRemove = files[index]; + setFiles(prev => prev.filter((_, i) => i !== index)); + if (fileToRemove && fileToRemove.name.toLowerCase().endsWith('.pdf')) { + setPdfFiles(prevPdfFiles => prevPdfFiles.filter(pdfFile => pdfFile.name !== fileToRemove.name)); + } + }; + + /** + * 上传文件 + */ + const uploadFiles = async () => { + if (files.length === 0) return; + + // 如果是第一次上传,直接走默认逻辑 + if (isFirstUpload) { + handleStartUpload('rebuild'); + return; + } + + // 否则打开领域树操作选择对话框 + setDomainTreeAction('upload'); + setPendingAction({ type: 'upload' }); + setDomainTreeActionOpen(true); + }; + + /** + * 处理领域树操作选择 + */ + const handleDomainTreeAction = action => { + setDomainTreeActionOpen(false); + + // 执行挂起的操作 + if (pendingAction && pendingAction.type === 'upload') { + handleStartUpload(action); + } else if (pendingAction && pendingAction.type === 'delete') { + handleDeleteFile(action); + } + + // 清除挂起的操作 + setPendingAction(null); + }; + + /** + * 开始上传文件 + */ + const handleStartUpload = async domainTreeActionType => { + setUploading(true); + try { + const uploadedFileInfos = []; + for (const file of files) { + const { fileContent, fileName } = await getContent(file); + const data = await fileApi.uploadFile({ file, projectId, fileContent, fileName, t }); + uploadedFileInfos.push({ fileName: data.fileName, fileId: data.fileId }); + } + toast.success(t('textSplit.uploadSuccess', { count: files.length })); + setFiles([]); + setCurrentPage(1); + await fetchUploadedFiles(); + if (onUploadSuccess) { + await onUploadSuccess(uploadedFileInfos, pdfFiles, domainTreeActionType); + } + } catch (err) { + toast.error(err.message || t('textSplit.uploadFailed')); + } finally { + setUploading(false); + } + }; + + // 打开删除确认对话框 + const openDeleteConfirm = (fileId, fileName) => { + setFileToDelete({ fileId, fileName }); + setDeleteConfirmOpen(true); + }; + + // 关闭删除确认对话框 + const closeDeleteConfirm = () => { + setDeleteConfirmOpen(false); + setFileToDelete(null); + }; + + // 删除文件前确认领域树操作 + const confirmDeleteFile = () => { + setDeleteConfirmOpen(false); + + // 如果没有其他文件了(删除后会变为空),直接删除 + if (uploadedFiles.total <= 1) { + handleDeleteFile('keep'); + return; + } + + // 否则打开领域树操作选择对话框 + setDomainTreeAction('delete'); + setPendingAction({ type: 'delete' }); + setDomainTreeActionOpen(true); + }; + + // 处理删除文件 + const handleDeleteFile = async domainTreeActionType => { + if (!fileToDelete) return; + + try { + setLoading(true); + closeDeleteConfirm(); + + await fileApi.deleteFile({ + fileToDelete, + projectId, + domainTreeActionType, + modelInfo: selectedModelInfo || {}, + t + }); + await fetchUploadedFiles(); + + if (onFileDeleted) { + const filesLength = uploadedFiles.total; + onFileDeleted(fileToDelete, filesLength); + } + + if (uploadedFiles.data && uploadedFiles.data.length <= 1 && currentPage > 1) { + setCurrentPage(1); + } + + toast.success(t('textSplit.deleteSuccess', { fileName: fileToDelete.fileName })); + } catch (error) { + console.error('Error deleting file:', error); + toast.error(error.message); + } finally { + setLoading(false); + setFileToDelete(null); + } + }; + + return ( + + {taskFileProcessing ? ( + + ) : ( + <> + + {/* 左侧:上传文件区域 */} + + + + + {/* 右侧:已上传文件列表 */} + + sendToPages(array)} + onDeleteFile={openDeleteConfirm} + projectId={projectId} + currentPage={currentPage} + onPageChange={(page, fileName) => { + if (fileName !== undefined) { + // 搜索时更新搜索关键词和页码 + setSearchFileName(fileName); + setCurrentPage(page); + } else { + // 翻页时只更新页码 + setCurrentPage(page); + } + }} + onRefresh={fetchUploadedFiles} // 传递刷新函数 + /> + + + + + + {/* 领域树操作选择对话框 */} + setDomainTreeActionOpen(false)} + onConfirm={handleDomainTreeAction} + isFirstUpload={isFirstUpload} + action={domainTreeAction} + /> + {/* 检测到pdf的处理框 */} + setpdfProcessConfirmOpen(false)} + onRadioChange={handleRadioChange} + value={pdfStrategy} + projectId={projectId} + taskSettings={taskSettings} + visionModels={visionModels} + selectedViosnModel={selectedViosnModel} + setSelectedViosnModel={setSelectedViosnModel} + /> + + )} + + ); +} diff --git a/easy-dataset-main/components/text-split/LoadingBackdrop.js b/easy-dataset-main/components/text-split/LoadingBackdrop.js new file mode 100644 index 0000000..3ba4a3c --- /dev/null +++ b/easy-dataset-main/components/text-split/LoadingBackdrop.js @@ -0,0 +1,114 @@ +'use client'; + +import { Backdrop, Paper, CircularProgress, Typography, Box, LinearProgress } from '@mui/material'; + +export default function LoadingBackdrop({ open, title, description, progress = null }) { + return ( + theme.zIndex.drawer + 1, + position: 'fixed', + backdropFilter: 'blur(5px)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }} + open={open} + > + + theme.palette.primary.main + }} + /> + + + {title} + + + + {description} + + + {progress && progress.total > 0 && ( + + + + {progress.completed}/{progress.total} ({progress.percentage}%) + + + {progress.questionCount > 0 && ( + + 已生成问题数: {progress.questionCount} + + )} + + + + + )} + + + ); +} diff --git a/easy-dataset-main/components/text-split/MarkdownViewDialog.js b/easy-dataset-main/components/text-split/MarkdownViewDialog.js new file mode 100644 index 0000000..fbbc325 --- /dev/null +++ b/easy-dataset-main/components/text-split/MarkdownViewDialog.js @@ -0,0 +1,433 @@ +'use client'; + +import React, { useState, useEffect, useRef } from 'react'; +import { + Box, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + CircularProgress, + Typography, + Divider, + Chip, + Switch, + FormControlLabel, + Alert, + DialogContentText +} from '@mui/material'; +import DeleteIcon from '@mui/icons-material/Delete'; +import SaveIcon from '@mui/icons-material/Save'; +import ReactMarkdown from 'react-markdown'; +import { useTranslation } from 'react-i18next'; +import 'github-markdown-css/github-markdown-light.css'; + +export default function MarkdownViewDialog({ open, text, onClose, projectId, onSaveSuccess }) { + const { t } = useTranslation(); + const [customSplitMode, setCustomSplitMode] = useState(false); + const [splitPoints, setSplitPoints] = useState([]); + const [selectedText, setSelectedText] = useState(''); + const [savedMessage, setSavedMessage] = useState(''); + const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(''); + const contentRef = useRef(null); + const [chunksPreview, setChunksPreview] = useState([]); + + // 根据分块点计算每个块的字数 + const calculateChunksPreview = points => { + if (!text || !text.content) return []; + + const content = text.content; + const sortedPoints = [...points].sort((a, b) => a.position - b.position); + + const chunks = []; + let startPos = 0; + + // 计算每个分块 + for (let i = 0; i < sortedPoints.length; i++) { + const endPos = sortedPoints[i].position; + const chunkContent = content.substring(startPos, endPos); + + if (chunkContent.trim().length > 0) { + chunks.push({ + index: i + 1, + length: chunkContent.length, + preview: chunkContent.substring(0, 20) + (chunkContent.length > 20 ? '...' : '') + }); + } + + startPos = endPos; + } + + // 添加最后一个分块 + const lastChunkContent = content.substring(startPos); + if (lastChunkContent.trim().length > 0) { + chunks.push({ + index: chunks.length + 1, + length: lastChunkContent.length, + preview: lastChunkContent.substring(0, 20) + (lastChunkContent.length > 20 ? '...' : '') + }); + } + + return chunks; + }; + + // 重置组件状态 + useEffect(() => { + if (!open) { + setSplitPoints([]); + setCustomSplitMode(false); + setSelectedText(''); + setSavedMessage(''); + } + }, [open]); + + // 当分块点变化时更新预览 + useEffect(() => { + if (splitPoints.length > 0 && text?.content) { + const preview = calculateChunksPreview(splitPoints); + setChunksPreview(preview); + } else { + setChunksPreview([]); + } + }, [splitPoints, text?.content]); + + // 处理用户选择文本事件 + const handleTextSelection = () => { + if (!customSplitMode) return; + + const selection = window.getSelection(); + if (!selection.toString().trim()) return; + + // 获取选择的文本内容和位置 + const selectedContent = selection.toString(); + + // 计算选择位置在文档中的偏移量 + const range = selection.getRangeAt(0); + const preCaretRange = range.cloneRange(); + preCaretRange.selectNodeContents(contentRef.current); + preCaretRange.setEnd(range.endContainer, range.endOffset); + const position = preCaretRange.toString().length; + + // 添加到分割点列表 + const newPoint = { + id: Date.now(), + position, + preview: selectedContent.substring(0, 40) + (selectedContent.length > 40 ? '...' : '') + }; + + setSplitPoints(prev => [...prev, newPoint].sort((a, b) => a.position - b.position)); + setSelectedText(''); + }; + + // 删除分割点 + const handleDeletePoint = id => { + setSplitPoints(prev => prev.filter(point => point.id !== id)); + }; + + // 弹出确认对话框 + const handleConfirmSave = () => { + setConfirmDialogOpen(true); + }; + + // 取消保存 + const handleCancelSave = () => { + setConfirmDialogOpen(false); + }; + + // 确认并执行保存 + const handleSavePoints = async () => { + // 输出调试信息 + console.log('保存分块点时的数据:', { + projectId, + text: text + ? { + fileId: text.fileId, + fileName: text.fileName, + contentLength: text.content ? text.content.length : 0 + } + : null, + splitPointsCount: splitPoints.length + }); + + if (!text) { + setError(t('textSplit.missingRequiredData') + ': text 为空'); + return; + } + + if (!text.fileId) { + setError(t('textSplit.missingRequiredData') + ': fileId 不存在'); + return; + } + + if (!text.fileName) { + setError(t('textSplit.missingRequiredData') + ': fileName 不存在'); + return; + } + + if (!text.content) { + setError(t('textSplit.missingRequiredData') + ': content 不存在'); + return; + } + + if (!projectId) { + setError(t('textSplit.missingRequiredData') + ': projectId 不存在'); + return; + } + + setConfirmDialogOpen(false); + setSaving(true); + setError(''); + + try { + // 准备要发送的数据 + const customSplitData = { + fileId: text.fileId, + fileName: text.fileName, + content: text.content, + splitPoints: splitPoints.map(point => ({ + position: point.position, + preview: point.preview + })) + }; + + // 发送请求到待创建的API接口 + const response = await fetch(`/api/projects/${projectId}/custom-split`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(customSplitData) + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(errorData.error || t('textSplit.customSplitFailed')); + } + + // 保存成功 + setSavedMessage(t('textSplit.customSplitSuccess')); + + // 短暂显示成功消息后关闭对话框并刷新列表 + setTimeout(() => { + setSavedMessage(''); + + // 关闭对话框 + onClose(); + + // 调用父组件的刷新方法(如果提供了) + if (typeof onSaveSuccess === 'function') { + onSaveSuccess(); + } + }, 1500); + } catch (err) { + console.error('保存自定义分块出错:', err); + setError(err.message || t('textSplit.customSplitFailed')); + } finally { + setSaving(false); + } + }; + + return ( + + + {text ? text.fileName : ''} + setCustomSplitMode(e.target.checked)} color="primary" /> + } + label={t('textSplit.customSplitMode')} + sx={{ ml: 2 }} + /> + + + {customSplitMode && ( + + + {t('textSplit.customSplitInstructions')} + + + {/* 分割点列表 */} + {splitPoints.length > 0 && ( + + + {t('textSplit.splitPointsList')} ({splitPoints.length}): + + + {splitPoints.map((point, index) => ( + handleDeletePoint(point.id)} + deleteIcon={} + color="primary" + variant="outlined" + /> + ))} + + + {/* 文本块字数预览 */} + {chunksPreview.length > 0 && ( + + + {t('textSplit.chunksPreview')} + + + {chunksPreview.map(chunk => ( + + ))} + + + )} + + )} + + {/* 保存按钮 */} + + + + + {/* 提示消息 */} + {savedMessage && ( + + {savedMessage} + + )} + + {error && ( + + {error} + + )} + + )} + + + + + {text ? ( + + {/* 渲染带有分割点标记的内容 */} + {customSplitMode && splitPoints.length > 0 ? ( + +
+                  {text.content.split('').map((char, index) => {
+                    const isSplitPoint = splitPoints.some(point => point.position === index);
+                    const splitPointIndex = splitPoints.findIndex(point => point.position === index);
+
+                    if (isSplitPoint) {
+                      return (
+                        
+                          
+                            
+                              {splitPointIndex + 1}
+                            
+                          
+                          {char}
+                        
+                      );
+                    }
+                    return char;
+                  })}
+                
+
+ ) : ( + +
+ {text.content} +
+
+ )} +
+ ) : ( + + + + )} +
+ + + + + + {/* 确认对话框 */} + + {t('textSplit.confirmCustomSplitTitle')} + + + {t('textSplit.confirmCustomSplitMessage')} + + + + + + + +
+ ); +} diff --git a/easy-dataset-main/components/text-split/PdfSettings.js b/easy-dataset-main/components/text-split/PdfSettings.js new file mode 100644 index 0000000..ae257e1 --- /dev/null +++ b/easy-dataset-main/components/text-split/PdfSettings.js @@ -0,0 +1,43 @@ +'use client'; + +import { Box, Select, MenuItem, Typography, FormControl, InputLabel } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export default function PdfSettings({ pdfStrategy, setPdfStrategy, selectedViosnModel, setSelectedViosnModel }) { + const { t } = useTranslation(); + + return ( + + + {t('textSplit.pdfStrategy')} + + + + {pdfStrategy === 'vision' && ( + + {t('textSplit.visionModel')} + + + )} + + ); +} diff --git a/easy-dataset-main/components/text-split/components/DeleteConfirmDialog.js b/easy-dataset-main/components/text-split/components/DeleteConfirmDialog.js new file mode 100644 index 0000000..353aee9 --- /dev/null +++ b/easy-dataset-main/components/text-split/components/DeleteConfirmDialog.js @@ -0,0 +1,60 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Button, + Typography, + Box, + Alert +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +export default function DeleteConfirmDialog({ open, fileName, onClose, onConfirm }) { + const { t } = useTranslation(); + return ( + + + {t('common.confirmDelete')}「{fileName}」? + + + {t('common.confirmDeleteDescription')} + + + + {t('textSplit.deleteFileWarning')} + + + + • {t('textSplit.deleteFileWarningChunks')} + + + • {t('textSplit.deleteFileWarningQuestions')} + + + • {t('textSplit.deleteFileWarningDatasets')} + + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/text-split/components/DirectoryView.js b/easy-dataset-main/components/text-split/components/DirectoryView.js new file mode 100644 index 0000000..3913fd4 --- /dev/null +++ b/easy-dataset-main/components/text-split/components/DirectoryView.js @@ -0,0 +1,73 @@ +'use client'; + +import { Box, List, ListItem, ListItemIcon, ListItemText, Collapse, IconButton } from '@mui/material'; +import FolderIcon from '@mui/icons-material/Folder'; +import ArticleIcon from '@mui/icons-material/Article'; +import ExpandLess from '@mui/icons-material/ExpandLess'; +import ExpandMore from '@mui/icons-material/ExpandMore'; +import { useTheme } from '@mui/material/styles'; + +/** + * 目录结构组件 + * @param {Object} props + * @param {Array} props.items - 目录项数组 + * @param {Object} props.expandedItems - 展开状态对象 + * @param {Function} props.onToggleItem - 展开/折叠回调 + * @param {number} props.level - 当前层级 + * @param {string} props.parentId - 父级ID + */ +export default function DirectoryView({ items, expandedItems, onToggleItem, level = 0, parentId = '' }) { + const theme = useTheme(); + + if (!items || items.length === 0) return null; + + return ( + 0 ? 2 : 0 }}> + {items.map((item, index) => { + const itemId = `${parentId}-${index}`; + const hasChildren = item.children && item.children.length > 0; + const isExpanded = expandedItems[itemId] || false; + + return ( + + 0 ? `1px solid ${theme.palette.divider}` : 'none', + ml: level > 0 ? 1 : 0 + }} + > + + {hasChildren ? : } + + + {hasChildren && ( + onToggleItem(itemId)}> + {isExpanded ? : } + + )} + + + {hasChildren && ( + + + + )} + + ); + })} + + ); +} diff --git a/easy-dataset-main/components/text-split/components/DomainTreeActionDialog.js b/easy-dataset-main/components/text-split/components/DomainTreeActionDialog.js new file mode 100644 index 0000000..2ef8bcf --- /dev/null +++ b/easy-dataset-main/components/text-split/components/DomainTreeActionDialog.js @@ -0,0 +1,101 @@ +'use client'; + +import { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Radio, + RadioGroup, + FormControlLabel, + FormControl, + Typography +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +/** + * 领域树操作选择对话框 + * 提供三种选项:修订领域树、重建领域树、不更改领域树 + */ +export default function DomainTreeActionDialog({ open, onClose, onConfirm, isFirstUpload, action }) { + const { t } = useTranslation(); + const [value, setValue] = useState(isFirstUpload ? 'rebuild' : 'revise'); + + // 处理选项变更 + const handleChange = event => { + setValue(event.target.value); + }; + + // 确认选择 + const handleConfirm = () => { + onConfirm(value); + }; + + // 获取对话框标题 + const getDialogTitle = () => { + if (isFirstUpload) { + return t('textSplit.domainTree.firstUploadTitle'); + } + return action === 'upload' ? t('textSplit.domainTree.uploadTitle') : t('textSplit.domainTree.deleteTitle'); + }; + + return ( + + {getDialogTitle()} + + + + {!isFirstUpload && ( + } + label={ + <> + {t('textSplit.domainTree.reviseOption')} + + {t('textSplit.domainTree.reviseDesc')} + + + } + /> + )} + } + label={ + <> + {t('textSplit.domainTree.rebuildOption')} + + {t('textSplit.domainTree.rebuildDesc')} + + + } + /> + {!isFirstUpload && ( + } + label={ + <> + {t('textSplit.domainTree.keepOption')} + + {t('textSplit.domainTree.keepDesc')} + + + } + /> + )} + + + + + + + + + ); +} diff --git a/easy-dataset-main/components/text-split/components/DomainTreeView.js b/easy-dataset-main/components/text-split/components/DomainTreeView.js new file mode 100644 index 0000000..b0e2a1e --- /dev/null +++ b/easy-dataset-main/components/text-split/components/DomainTreeView.js @@ -0,0 +1,33 @@ +'use client'; + +import { Box } from '@mui/material'; +import { TreeView, TreeItem } from '@mui/lab'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import ChevronRightIcon from '@mui/icons-material/ChevronRight'; + +/** + * 领域知识树组件 + * @param {Object} props + * @param {Array} props.nodes - 树节点数组 + */ +export default function DomainTreeView({ nodes = [] }) { + if (!nodes || nodes.length === 0) return null; + + const renderTreeItems = nodes => { + return nodes.map((node, index) => ( + + {node.children && node.children.length > 0 && renderTreeItems(node.children)} + + )); + }; + + return ( + } + defaultExpandIcon={} + sx={{ flexGrow: 1, overflowY: 'auto' }} + > + {renderTreeItems(nodes)} + + ); +} diff --git a/easy-dataset-main/components/text-split/components/FileList.js b/easy-dataset-main/components/text-split/components/FileList.js new file mode 100644 index 0000000..e494e2b --- /dev/null +++ b/easy-dataset-main/components/text-split/components/FileList.js @@ -0,0 +1,1068 @@ +'use client'; + +import { + Box, + Typography, + List, + ListItem, + ListItemText, + IconButton, + Tooltip, + Divider, + CircularProgress, + Checkbox, + Button, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + FormControlLabel, + Switch, + Pagination, + TextField, + InputAdornment, + Grid, + Alert +} from '@mui/material'; +import { + Visibility as VisibilityIcon, + Download, + Delete as DeleteIcon, + FilePresent as FileIcon, + Psychology as PsychologyIcon, + CheckBox as SelectAllIcon, + CheckBoxOutlineBlank as DeselectAllIcon, + Search as SearchIcon, + Clear as ClearIcon +} from '@mui/icons-material'; +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useAtomValue } from 'jotai'; +import { selectedModelInfoAtom } from '@/lib/store'; +import MarkdownViewDialog from '../MarkdownViewDialog'; +import GaPairsIndicator from '../../mga/GaPairsIndicator'; +import DomainTreeActionDialog from './DomainTreeActionDialog'; +import i18n from '@/lib/i18n'; +import { toast } from 'sonner'; + +export default function FileList({ + theme, + files = {}, + loading = false, + onDeleteFile, + sendToFileUploader, + projectId, + setPageLoading, + currentPage = 1, + onPageChange, + onRefresh, // 新增:刷新文件列表的回调函数 + isFullscreen = false // 新增参数,用于控制是否处于全屏状态 +}) { + const { t } = useTranslation(); + + // 现有的状态 + const [array, setArray] = useState([]); + const [viewDialogOpen, setViewDialogOpen] = useState(false); + const [viewContent, setViewContent] = useState(''); + + // 新增的批量生成GA对相关状态 + const [batchGenDialogOpen, setBatchGenDialogOpen] = useState(false); + const [generating, setGenerating] = useState(false); + const [genError, setGenError] = useState(null); + const [genResult, setGenResult] = useState(null); + const [projectModel, setProjectModel] = useState(null); + const [loadingModel, setLoadingModel] = useState(false); + const [appendMode, setAppendMode] = useState(false); + const [generationMode, setGenerationMode] = useState('ai'); // 'ai' 或 'manual' + const [manualGaPair, setManualGaPair] = useState({ + genreTitle: '', + genreDesc: '', + audienceTitle: '', + audienceDesc: '' + }); + + // 批量删除相关状态 + const [batchDeleteDialogOpen, setBatchDeleteDialogOpen] = useState(false); + const [domainTreeActionOpen, setDomainTreeActionOpen] = useState(false); + const [deleting, setDeleting] = useState(false); + + // 搜索相关状态 + const [searchTerm, setSearchTerm] = useState(''); + const [searchLoading, setSearchLoading] = useState(false); + + // 获取当前选中的模型信息 + const selectedModelInfo = useAtomValue(selectedModelInfoAtom); + + // 后端搜索功能 + const handleSearch = async searchValue => { + if (typeof onPageChange === 'function') { + setSearchLoading(true); + try { + // 调用父组件的页面变更函数,传递搜索参数 + await onPageChange(1, searchValue); // 搜索时重置到第一页 + } catch (error) { + console.error('搜索失败:', error); + } finally { + setSearchLoading(false); + } + } + }; + + // 防抖搜索 + useEffect(() => { + const timer = setTimeout(() => { + handleSearch(searchTerm); + }, 500); // 500ms 防抖 + + return () => clearTimeout(timer); + }, [searchTerm]); + + // 清空搜索 + const handleClearSearch = () => { + setSearchTerm(''); + // 清空搜索时立即触发搜索 + handleSearch(''); + }; + + const handleCheckboxChange = (fileId, isChecked) => { + setArray(prevArray => { + let newArray; + const stringFileId = String(fileId); + + if (isChecked) { + newArray = prevArray.includes(stringFileId) ? prevArray : [...prevArray, stringFileId]; + } else { + newArray = prevArray.filter(item => item !== stringFileId); + } + + if (typeof sendToFileUploader === 'function') { + sendToFileUploader(newArray); + } + return newArray; + }); + }; + + // 全选文件(包括所有页面的文件) + const handleSelectAll = async () => { + try { + // 获取项目中所有文件的ID + const response = await fetch(`/api/projects/${projectId}/files?getAllIds=true`); + if (!response.ok) { + throw new Error('获取文件列表失败'); + } + + const data = await response.json(); + const allFileIds = data.allFileIds || []; + + setArray(allFileIds); + if (typeof sendToFileUploader === 'function') { + sendToFileUploader(allFileIds); + } + } catch (error) { + console.error('全选文件失败:', error); + // 如果API调用失败,回退到选择当前页面的文件 + if (files?.data?.length > 0) { + const currentPageFileIds = files.data.map(file => String(file.id)); + setArray(currentPageFileIds); + if (typeof sendToFileUploader === 'function') { + sendToFileUploader(currentPageFileIds); + } + } + } + }; + // 取消全选 + const handleDeselectAll = () => { + setArray([]); + if (typeof sendToFileUploader === 'function') { + sendToFileUploader([]); + } + }; + + const handleCloseViewDialog = () => { + setViewDialogOpen(false); + }; + + // 刷新文本块列表 + const refreshTextChunks = () => { + if (typeof setPageLoading === 'function') { + setPageLoading(true); + setTimeout(() => { + // 可能需要调用父组件的刷新方法 + sendToFileUploader(array); + setPageLoading(false); + }, 500); + } + }; + + const handleViewContent = async fileId => { + getFileContent(fileId); + setViewDialogOpen(true); + }; + + const handleDownload = async (fileId, fileName) => { + setPageLoading(true); + const text = await getFileContent(fileId); + + // Modify the filename if it ends with .pdf + let downloadName = fileName || 'download.txt'; + if (downloadName.toLowerCase().endsWith('.pdf')) { + downloadName = downloadName.slice(0, -4) + '.md'; + } + + const blob = new Blob([text.content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + + const a = document.createElement('a'); + a.href = url; + a.download = downloadName; + + document.body.appendChild(a); + a.click(); + + document.body.removeChild(a); + URL.revokeObjectURL(url); + + setPageLoading(false); + }; + + const getFileContent = async fileId => { + try { + const response = await fetch(`/api/projects/${projectId}/preview/${fileId}`); + if (!response.ok) { + throw new Error(t('textSplit.fetchChunksFailed')); + } + const data = await response.json(); + setViewContent(data); + return data; + } catch (error) { + console.error(t('textSplit.fetchChunksError'), error); + } + }; + + const formatFileSize = size => { + if (size < 1024) { + return size + 'B'; + } else if (size < 1024 * 1024) { + return (size / 1024).toFixed(2) + 'KB'; + } else if (size < 1024 * 1024 * 1024) { + return (size / 1024 / 1024).toFixed(2) + 'MB'; + } else { + return (size / 1024 / 1024 / 1024).toFixed(2) + 'GB'; + } + }; + + // 新增:获取项目特定的默认模型信息 + const fetchProjectModel = async () => { + try { + setLoadingModel(true); + + // 首先获取项目信息 + const response = await fetch(`/api/projects/${projectId}`); + if (!response.ok) { + throw new Error(t('gaPairs.fetchProjectInfoFailed', { status: response.status })); + } + + const projectData = await response.json(); + + // 获取模型配置 + const modelResponse = await fetch(`/api/projects/${projectId}/model-config`); + if (!modelResponse.ok) { + throw new Error(t('gaPairs.fetchModelConfigFailed', { status: modelResponse.status })); + } + + const modelConfigData = await modelResponse.json(); + + if (modelConfigData.data && Array.isArray(modelConfigData.data)) { + // 优先使用项目默认模型 + let targetModel = null; + + if (projectData.defaultModelConfigId) { + targetModel = modelConfigData.data.find(model => model.id === projectData.defaultModelConfigId); + } + + // 如果没有默认模型,使用第一个可用的模型 + if (!targetModel) { + targetModel = modelConfigData.data.find( + m => m.modelName && m.endpoint && (m.providerId === 'ollama' || m.apiKey) + ); + } + + if (targetModel) { + setProjectModel(targetModel); + } + } + } catch (error) { + console.error(t('gaPairs.fetchProjectModelError'), error); + } finally { + setLoadingModel(false); + } + }; + + // 新增:批量生成GA对的处理函数 + const handleBatchGenerateGAPairs = async () => { + if (array.length === 0) { + setGenError(t('gaPairs.selectAtLeastOneFile')); + return; + } + + // 如果是手动添加模式,验证手动输入的 GA 对 + if (generationMode === 'manual') { + if (!manualGaPair.genreTitle || !manualGaPair.audienceTitle) { + setGenError(t('gaPairs.manualGaPairRequired')); + return; + } + + try { + setGenerating(true); + setGenError(null); + setGenResult(null); + + const stringFileIds = array.map(id => String(id)); + + const requestData = { + fileIds: stringFileIds, + gaPair: manualGaPair, + appendMode: appendMode + }; + + const response = await fetch(`/api/projects/${projectId}/batch-add-manual-ga`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(requestData) + }); + + const responseText = await response.text(); + + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: t('gaPairs.requestFailed', { status: response.status }) })); + throw new Error(errorData.error || t('gaPairs.requestFailed', { status: response.status })); + } + + const result = JSON.parse(responseText); + + if (result.success) { + setGenResult({ + total: result.data?.length || 0, + success: result.data?.filter(r => r.success).length || 0 + }); + + // 成功后清空选择状态和表单 + setArray([]); + if (typeof sendToFileUploader === 'function') { + sendToFileUploader([]); + } + setManualGaPair({ + genreTitle: '', + genreDesc: '', + audienceTitle: '', + audienceDesc: '' + }); + + // 发送全局刷新事件 + const successfulFileIds = result.data?.filter(item => item.success)?.map(item => String(item.fileId)) || []; + + if (successfulFileIds.length > 0) { + window.dispatchEvent( + new CustomEvent('refreshGaPairsIndicators', { + detail: { + projectId, + fileIds: successfulFileIds + } + }) + ); + } + } else { + setGenError(result.error || t('gaPairs.generationFailed')); + } + } catch (error) { + console.error(t('gaPairs.batchGenerationFailed'), error); + setGenError(t('gaPairs.generationError', { error: error.message || t('common.unknownError') })); + } finally { + setGenerating(false); + } + return; + } + + // AI 生成模式 + const modelToUse = projectModel || selectedModelInfo; + + if (!modelToUse || !modelToUse.id) { + setGenError(t('gaPairs.noDefaultModel')); + return; + } + + // 检查模型配置是否完整 + if (!modelToUse.modelName || !modelToUse.endpoint) { + setGenError('模型配置不完整,请检查模型设置'); + return; + } + + // 检查API密钥(除了ollama模型) + if (modelToUse.providerId !== 'ollama' && !modelToUse.apiKey) { + setGenError(t('gaPairs.missingApiKey')); + return; + } + + try { + setGenerating(true); + setGenError(null); + setGenResult(null); + + const stringFileIds = array.map(id => String(id)); + + // 获取当前语言环境 + const currentLanguage = i18n.language === 'en' ? 'en' : '中文'; + + const requestData = { + fileIds: stringFileIds, + modelConfigId: modelToUse.id, + language: currentLanguage, + appendMode: appendMode + }; + + const response = await fetch(`/api/projects/${projectId}/batch-generateGA`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(requestData) + }); + + const responseText = await response.text(); + + if (!response.ok) { + const errorData = await response + .json() + .catch(() => ({ error: t('gaPairs.requestFailed', { status: response.status }) })); + throw new Error(errorData.error || t('gaPairs.requestFailed', { status: response.status })); + } + + const result = JSON.parse(responseText); + + if (result.success) { + setGenResult({ + total: result.data?.length || 0, + success: result.data?.filter(r => r.success).length || 0 + }); + + // 成功后清空选择状态 + setArray([]); + if (typeof sendToFileUploader === 'function') { + sendToFileUploader([]); + } + + console.log(t('gaPairs.batchGenerationSuccess', { count: result.summary?.success || 0 })); + + //发送全局刷新事件 + const successfulFileIds = result.data?.filter(item => item.success)?.map(item => String(item.fileId)) || []; + + if (successfulFileIds.length > 0) { + window.dispatchEvent( + new CustomEvent('refreshGaPairsIndicators', { + detail: { + projectId, + fileIds: successfulFileIds + } + }) + ); + } + } else { + setGenError(result.error || t('gaPairs.generationFailed')); + } + } catch (error) { + console.error(t('gaPairs.batchGenerationFailed'), error); + setGenError(t('gaPairs.generationError', { error: error.message || t('common.unknownError') })); + } finally { + setGenerating(false); + } + }; + + // 新增:打开批量生成对话框 + const openBatchGenDialog = () => { + // 如果没有选中文件,自动选中所有文件 + if (array.length === 0 && files?.data?.length > 0) { + const allFileIds = files.data.map(file => String(file.id)); + setArray(allFileIds); + if (typeof sendToFileUploader === 'function') { + sendToFileUploader(allFileIds); + } + } + + // 获取项目模型配置 + fetchProjectModel(); + setBatchGenDialogOpen(true); + }; + + // 新增:关闭批量生成对话框 + const closeBatchGenDialog = () => { + setBatchGenDialogOpen(false); + setGenError(null); + setGenResult(null); + setAppendMode(false); // 重置追加模式 + }; + + // 批量删除处理函数 - 第一步:打开确认对话框 + const handleBatchDelete = () => { + if (array.length === 0) { + return; + } + setBatchDeleteDialogOpen(true); + }; + + // 确认批量删除 - 第二步:打开领域树选择对话框 + const confirmBatchDelete = () => { + setBatchDeleteDialogOpen(false); + + // 检查是否还有其他文件 + const remainingFilesCount = files.total - array.length; + + // 如果删除后没有文件了,直接执行删除(keep 模式) + if (remainingFilesCount === 0) { + executeBatchDelete('keep'); + return; + } + + // 否则打开领域树操作选择对话框 + setDomainTreeActionOpen(true); + }; + + // 处理领域树操作选择 + const handleDomainTreeAction = action => { + setDomainTreeActionOpen(false); + executeBatchDelete(action); + }; + + // 执行批量删除 - 第三步:实际删除操作 + const executeBatchDelete = async domainTreeAction => { + if (array.length === 0) { + return; + } + + setDeleting(true); + // 设置页面 loading 状态 + if (typeof setPageLoading === 'function') { + setPageLoading(true); + } + + try { + const response = await fetch(`/api/projects/${projectId}/batch-delete-files`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + fileIds: array, + domainTreeAction, + model: selectedModelInfo || {}, + language: i18n.language === 'en' ? 'English' : '中文' + }) + }); + + if (!response.ok) { + throw new Error('批量删除失败'); + } + + const result = await response.json(); + + // 清空选择 + setArray([]); + if (typeof sendToFileUploader === 'function') { + sendToFileUploader([]); + } + + // 刷新文件列表 + if (typeof onRefresh === 'function') { + await onRefresh(); + } else if (typeof onPageChange === 'function') { + // 回退方案:如果没有 onRefresh,使用 onPageChange + await onPageChange(1); + } + + toast.success( + t('textSplit.batchDeleteSuccess', { + count: result.deletedCount || array.length, + defaultValue: `成功删除 ${result.deletedCount || array.length} 个文件` + }) + ); + } catch (error) { + console.error('批量删除文件失败:', error); + toast.error(t('textSplit.batchDeleteFailed', { defaultValue: '批量删除失败' })); + } finally { + setDeleting(false); + // 清除页面 loading 状态 + if (typeof setPageLoading === 'function') { + setPageLoading(false); + } + } + }; + + // 取消批量删除 + const cancelBatchDelete = () => { + setBatchDeleteDialogOpen(false); + }; + + return ( + + {/* 标题和按钮区域 */} + + {/* 第一行:标题和按钮 */} + 0 ? 2 : 0 + }} + > + {t('textSplit.uploadedDocuments', { count: files.total })} + + {/* 批量操作按钮 */} + {files.total > 0 && ( + + {/* 全选/取消全选按钮 */} + {array.length === files.total ? ( + + ) : ( + + )} + + {/* 批量删除按钮 */} + {array.length > 0 && ( + + )} + + {/* 批量生成GA对按钮 */} + + + )} + + + {/* 第二行:搜索框 - 在全屏展示时显示,或者有搜索内容时显示 */} + {isFullscreen && (files.total > 0 || searchTerm) && ( + + setSearchTerm(e.target.value)} + InputProps={{ + startAdornment: ( + + + + ), + endAdornment: searchTerm && ( + + + + + + ) + }} + sx={{ width: '100%', maxWidth: 400 }} + /> + {(searchTerm || searchLoading) && ( + + {searchLoading + ? '搜索中...' + : searchTerm + ? t('textSplit.searchResults', { + count: files?.data?.length || 0, + total: files.total, + defaultValue: `找到 ${files?.data?.length || 0} 个文件(共 ${files.total} 个)` + }) + : null} + + )} + + )} + + + {loading ? ( + + + + ) : files.total === 0 ? ( + + + {searchTerm + ? // 搜索无结果 + t('textSplit.noSearchResults', { + searchTerm, + defaultValue: `未找到包含 "${searchTerm}" 的文件` + }) + : // 真的没有上传文件 + t('textSplit.noFilesUploaded', { + defaultValue: '暂未上传文件' + })} + + + ) : !files?.data || files.data.length === 0 ? ( + + + {searchTerm + ? // 搜索有结果但当前页没数据 + t('textSplit.noResultsOnCurrentPage', { + defaultValue: '当前页面没有搜索结果,请返回第一页查看' + }) + : // 当前页没数据但总数不为0 + t('textSplit.noDataOnCurrentPage', { + defaultValue: '当前页面没有数据' + })} + + + ) : ( + <> + + {files?.data?.map((file, index) => ( + + + {/* 文件信息区域 */} + + + + handleViewContent(file.id)} + primary={ + + {file.fileName} + + } + secondary={ + + {`${formatFileSize(file.size)} · ${new Date(file.createAt).toLocaleString()}`} + + } + /> + + + + {/* 操作按钮区域 */} + + handleCheckboxChange(file.id, e.target.checked)} + /> + + + handleDownload(file.id, file.fileName)}> + + + + + onDeleteFile(file.id, file.fileName)}> + + + + + + {index < files.data.length - 1 && } + + ))} + + + {/* 分页控件 */} + {files.total > 10 && ( + + onPageChange && onPageChange(page)} + color="primary" + showFirstButton + showLastButton + /> + + )} + + )} + + {/* 现有的文本块详情对话框 */} + + + {/* 新增:批量生成GA对对话框 */} + + {t('gaPairs.batchGenerateTitle')} + + {!genResult && ( + + {t('gaPairs.batchGenerateDescription', { count: array.length })} + + {/* 生成方式选择 */} + + + {t('gaPairs.generationMode')} + + setGenerationMode(e.target.checked ? 'manual' : 'ai')} + color="primary" + /> + } + label={generationMode === 'manual' ? t('gaPairs.manualAddMode') : t('gaPairs.aiGenerateMode')} + /> + + + {/* AI 生成模式:显示模型信息 */} + {generationMode === 'ai' && ( + <> + {loadingModel ? ( + + + {t('gaPairs.loadingProjectModel')} + + ) : projectModel ? ( + + + {t('gaPairs.usingModel')}:{' '} + + {projectModel.providerName}: {projectModel.modelName} + + + + ) : ( + + + {t('gaPairs.noDefaultModel')} + + + )} + + )} + + {/* 手动添加模式:显示输入表单 */} + {generationMode === 'manual' && ( + + + + setManualGaPair({ ...manualGaPair, genreTitle: e.target.value })} + required + /> + + + setManualGaPair({ ...manualGaPair, genreDesc: e.target.value })} + multiline + rows={2} + /> + + + setManualGaPair({ ...manualGaPair, audienceTitle: e.target.value })} + required + /> + + + setManualGaPair({ ...manualGaPair, audienceDesc: e.target.value })} + multiline + rows={2} + /> + + + + )} + + {/* 追加模式选择 */} + + setAppendMode(e.target.checked)} color="primary" /> + } + label={`${t('gaPairs.appendMode')}(${t('gaPairs.appendModeDescription')})`} + /> + + + )} + + {genError && ( + + + {genError} + + + )} + + {genResult && ( + + + {t('gaPairs.batchGenCompleted', { success: genResult.success, total: genResult.total })} + + + )} + + + + {!genResult && ( + + )} + + + + {/* 批量删除确认对话框 */} + + {t('textSplit.batchDeleteTitle')} + + + {t('textSplit.batchDeleteConfirm', { + count: array.length, + defaultValue: `确定要删除选中的 ${array.length} 个文件吗?此操作不可恢复。` + })} + + + + {t('textSplit.deleteFileWarning')} + + + + • {t('textSplit.deleteFileWarningChunks')} + + + • {t('textSplit.deleteFileWarningQuestions')} + + + • {t('textSplit.deleteFileWarningDatasets')} + + + + + + + + + + + {/* 领域树操作选择对话框 */} + setDomainTreeActionOpen(false)} + onConfirm={handleDomainTreeAction} + isFirstUpload={false} + action="delete" + /> + + ); +} diff --git a/easy-dataset-main/components/text-split/components/FileLoadingProgress.js b/easy-dataset-main/components/text-split/components/FileLoadingProgress.js new file mode 100644 index 0000000..aeb1403 --- /dev/null +++ b/easy-dataset-main/components/text-split/components/FileLoadingProgress.js @@ -0,0 +1,208 @@ +'use client'; + +import { Box, Typography, keyframes, Paper } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { handleLongFileName } from '@/lib/file/file-process'; +import { useState, useEffect } from 'react'; + +// 定义动画效果 +const pulse = keyframes` + 0% { + box-shadow: 0 0 0 0 rgba(32, 76, 255, 0.2); + } + 70% { + box-shadow: 0 0 0 15px rgba(32, 76, 255, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(32, 76, 255, 0); + } +`; + +const rotateAnimation = keyframes` + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +`; + +const shimmer = keyframes` + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } +`; + +/** + * 文件处理进度展示组件 - 美化版 + * + * @param {Object} props + * @param {Object} props.fileTask - 文件处理任务信息 + */ +export default function FileLoadingProgress({ fileTask }) { + const { t } = useTranslation(); + const [animationStep, setAnimationStep] = useState(0); + + // 创建动态效果 + useEffect(() => { + const interval = setInterval(() => { + setAnimationStep(prev => (prev + 1) % 4); + }, 600); + return () => clearInterval(interval); + }, []); + + if (!fileTask) { + return null; + } + + const pageProgress = (fileTask.current.processedPage / fileTask.current.totalPage) * 100; + const filesProgress = (fileTask.processedFiles / fileTask.totalFiles) * 100; + + // 生成进度指示器文本 + const getProgressIndicator = () => { + const dots = '.'; + return dots.repeat(animationStep + 1); + }; + + return ( + + {/* 背景动画元素 */} + + + {/* 主标题 */} + + {t('textSplit.pdfProcessingLoading')} + {getProgressIndicator()} + + + {/* 处理进度显示区域 */} + + {/* 当前文件进度 */} + + + {/* 总文件进度 */} + + + + ); +} + +/** + * 进度条区域组件 + */ +function ProgressSection({ label, progress, color, mt = 0 }) { + return ( + + + + {label} + + + {Math.round(progress)}% + + + + {/* 自定义进度条 */} + + + + + ); +} diff --git a/easy-dataset-main/components/text-split/components/PdfProcessingDialog.js b/easy-dataset-main/components/text-split/components/PdfProcessingDialog.js new file mode 100644 index 0000000..782cffd --- /dev/null +++ b/easy-dataset-main/components/text-split/components/PdfProcessingDialog.js @@ -0,0 +1,188 @@ +'use client'; + +import { + Dialog, + DialogTitle, + DialogContent, + Card, + CardContent, + Typography, + Box, + Stack, + FormControl, + InputLabel, + Select, + MenuItem +} from '@mui/material'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { styled } from '@mui/material/styles'; +import ArticleOutlinedIcon from '@mui/icons-material/ArticleOutlined'; +import ScienceOutlinedIcon from '@mui/icons-material/ScienceOutlined'; +import LaunchOutlinedIcon from '@mui/icons-material/LaunchOutlined'; +import SmartToyOutlinedIcon from '@mui/icons-material/SmartToyOutlined'; +import ChangeCircleOutlinedIcon from '@mui/icons-material/ChangeCircleOutlined'; + +const StyledCard = styled(Card)(({ theme, disabled }) => ({ + cursor: disabled ? 'not-allowed' : 'pointer', + opacity: disabled ? 0.6 : 1, + transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out', + '&:hover': disabled + ? {} + : { + transform: 'translateY(-4px)', + boxShadow: theme.shadows[4] + } +})); + +const OptionCard = ({ + icon, + title, + description, + disabled, + onClick, + selected, + isVisionEnabled, + visionModels, + selectorName, + handleSettingChange, + selectedViosnModel +}) => ( + + + + {icon} + + {title} + + + {description} + + {isVisionEnabled && ( + + {selectorName} + + + )} + + + +); + +export default function PdfProcessingDialog({ + open, + onClose, + onRadioChange, + value, + taskSettings, + visionModels, + selectedViosnModel, + setSelectedViosnModel +}) { + const { t } = useTranslation(); + + //检查配置中是否启用MinerU + const isMinerUEnabled = taskSettings && taskSettings.minerUToken ? true : false; + + const isMinerULocalEnabled = taskSettings && taskSettings.minerULocalUrl ? true : false; + + //检查配置中是否启用Vision策略 + const isVisionEnabled = visionModels.length > 0 ? true : false; + + //用于传递到父组件,显示当前选中的模型 + let selectedModel = selectedViosnModel; + + const handleOptionClick = optionValue => { + if (optionValue === 'mineru-web') { + window.open('https://mineru.net/OpenSourceTools/Extractor', '_blank'); + } else { + onRadioChange({ target: { value: optionValue, selectedVision: selectedModel } }); + onClose(); + } + }; + + // 处理设置变更 + const handleSettingChange = e => { + const { value } = e.target; + selectedModel = value; + setSelectedViosnModel(value); + }; + + return ( + + {t('textSplit.pdfProcess')} + + + } + title={t('textSplit.basicPdfParsing')} + description={t('textSplit.basicPdfParsingDesc')} + onClick={() => handleOptionClick('default')} + selected={value === 'default'} + /> + } + title="MinerU API" + description={isMinerUEnabled ? t('textSplit.mineruApiDesc') : t('textSplit.mineruApiDescDisabled')} + disabled={!isMinerUEnabled} + onClick={() => handleOptionClick('mineru')} + selected={value === 'mineru'} + /> + } + title="MinerU Local" + description={isMinerULocalEnabled ? t('textSplit.mineruLocalDesc') : t('textSplit.mineruLocalDisabled')} + disabled={!isMinerULocalEnabled} + onClick={() => handleOptionClick('mineru-local')} + selected={value === 'mineru-local'} + /> + } + title={t('textSplit.mineruWebPlatform')} + description={t('textSplit.mineruWebPlatformDesc')} + onClick={() => handleOptionClick('mineru-web')} + /> + } + title={t('textSplit.customVisionModel')} + description={t('textSplit.customVisionModelDesc')} + disabled={!isVisionEnabled} + onClick={() => handleOptionClick('vision')} + selected={value === 'vision'} + isVisionEnabled={isVisionEnabled} + visionModels={visionModels} + selectorName={t('settings.vision')} + selectedViosnModel={selectedViosnModel} + handleSettingChange={handleSettingChange} + /> + + + + ); +} diff --git a/easy-dataset-main/components/text-split/components/TabPanel.js b/easy-dataset-main/components/text-split/components/TabPanel.js new file mode 100644 index 0000000..2f2b7e5 --- /dev/null +++ b/easy-dataset-main/components/text-split/components/TabPanel.js @@ -0,0 +1,24 @@ +'use client'; + +import { Box } from '@mui/material'; + +/** + * 标签页面板组件 + * @param {Object} props + * @param {number} props.value - 当前激活的标签索引 + * @param {number} props.index - 当前面板对应的索引 + * @param {ReactNode} props.children - 子组件 + */ +export default function TabPanel({ value, index, children }) { + return ( + + ); +} diff --git a/easy-dataset-main/components/text-split/components/UploadArea.js b/easy-dataset-main/components/text-split/components/UploadArea.js new file mode 100644 index 0000000..3e98cfd --- /dev/null +++ b/easy-dataset-main/components/text-split/components/UploadArea.js @@ -0,0 +1,207 @@ +'use client'; + +import { + Box, + Button, + Typography, + List, + ListItem, + ListItemText, + Divider, + CircularProgress, + Tooltip +} from '@mui/material'; +import UploadFileIcon from '@mui/icons-material/UploadFile'; +import DeleteIcon from '@mui/icons-material/Delete'; +import { alpha } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; +import React, { useRef, useState } from 'react'; + +export default function UploadArea({ + theme, + files, + uploading, + uploadedFiles, + onFileSelect, + onRemoveFile, + onUpload, + selectedModel +}) { + const { t } = useTranslation(); + const [dragActive, setDragActive] = useState(false); + const inputRef = useRef(null); + + // 拖拽进入 + const handleDragOver = e => { + e.preventDefault(); + e.stopPropagation(); + if (!dragActive) setDragActive(true); + }; + // 拖拽离开 + const handleDragLeave = e => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + }; + // 拖拽释放 + const handleDrop = e => { + e.preventDefault(); + e.stopPropagation(); + setDragActive(false); + if (!selectedModel?.id || uploading) return; + const files = e.dataTransfer.files; + if (files && files.length > 0) { + // 构造一个模拟的 event 以复用 onFileSelect + const event = { target: { files } }; + onFileSelect(event); + } + }; + + return ( + + {dragActive && ( + + + + + {t('textSplit.dragToUpload', { defaultValue: '拖拽文件到此处上传' })} + + + + )} + + {t('textSplit.uploadNewDocument')} + + + + + + + + + + {uploadedFiles.total > 0 ? t('textSplit.mutilFileMessage') : t('textSplit.supportedFormats')} + + + {files.length > 0 && ( + + + {t('textSplit.selectedFiles', { count: files.length })} + + + + {files.map((file, index) => ( + + } + onClick={() => onRemoveFile(index)} + disabled={uploading} + > + {t('common.delete')} + + } + > + + + {index < files.length - 1 && } + + ))} + + + + + + + + + + + )} + + ); +} diff --git a/easy-dataset-main/constant/index.js b/easy-dataset-main/constant/index.js new file mode 100644 index 0000000..9a41497 --- /dev/null +++ b/easy-dataset-main/constant/index.js @@ -0,0 +1,15 @@ +/** + * 全局常量 + */ + +export const FILE = { + MAX_FILE_SIZE: 300 * 1024 * 1024 // 300MB in bytes +}; + +export const TASK = { + STATUS: { + PROCESSING: 0, + COMPLETED: 1, + FAILED: 2 + } +}; diff --git a/easy-dataset-main/constant/model.js b/easy-dataset-main/constant/model.js new file mode 100644 index 0000000..dfc2351 --- /dev/null +++ b/easy-dataset-main/constant/model.js @@ -0,0 +1,83 @@ +export const MODEL_PROVIDERS = [ + { + id: 'ollama', + name: 'Ollama', + defaultEndpoint: 'http://127.0.0.1:11434/api', + defaultModels: [] + }, + { + id: 'openai', + name: 'OpenAI', + defaultEndpoint: 'https://api.openai.com/v1/', + defaultModels: ['gpt-4o', 'gpt-4o-mini', 'o1-mini'] + }, + { + id: 'siliconcloud', + name: '硅基流动', + defaultEndpoint: 'https://api.siliconflow.cn/v1/', + defaultModels: [ + 'deepseek-ai/DeepSeek-R1', + 'deepseek-ai/DeepSeek-V3', + 'Qwen2.5-7B-Instruct', + 'meta-llama/Llama-3.3-70B-Instruct' + ] + }, + { + id: 'deepseek', + name: 'DeepSeek', + defaultEndpoint: 'https://api.deepseek.com/v1/', + defaultModels: ['deepseek-chat', 'deepseek-reasoner'] + }, + { + id: '302ai', + name: '302.AI', + defaultEndpoint: 'https://api.302.ai/v1/', + defaultModels: ['Doubao-pro-128k', 'deepseek-r1', 'kimi-latest', 'qwen-max'] + }, + { + id: 'zhipu', + name: '智谱AI', + defaultEndpoint: 'https://open.bigmodel.cn/api/paas/v4/', + defaultModels: ['glm-4-flash', 'glm-4-flashx', 'glm-4-plus', 'glm-4-long'] + }, + { + id: 'Doubao', + name: '火山引擎', + defaultEndpoint: 'https://ark.cn-beijing.volces.com/api/v3/', + defaultModels: [] + }, + { + id: 'groq', + name: 'Groq', + defaultEndpoint: 'https://api.groq.com/openai', + defaultModels: ['Gemma 7B', 'LLaMA3 8B', 'LLaMA3 70B'] + }, + { + id: 'grok', + name: 'Grok', + defaultEndpoint: 'https://api.x.ai/v1', + defaultModels: ['Grok'] + }, + { + id: 'OpenRouter', + name: 'OpenRouter', + defaultEndpoint: 'https://openrouter.ai/api/v1/', + defaultModels: [ + 'google/gemma-2-9b-it:free', + 'meta-llama/llama-3-8b-instruct:free', + 'microsoft/phi-3-mini-128k-instruct:free' + ] + }, + { + id: 'alibailian', + name: '阿里云百炼', + defaultEndpoint: 'https://dashscope.aliyuncs.com/compatible-mode/v1', + defaultModels: ['qwen-max-latest', 'qwen-max-2025-01-25'] + } +]; + +export const DEFAULT_MODEL_SETTINGS = { + temperature: 0.7, + maxTokens: 8192, + topP: 0.9 +}; diff --git a/easy-dataset-main/constant/setting.js b/easy-dataset-main/constant/setting.js new file mode 100644 index 0000000..894c631 --- /dev/null +++ b/easy-dataset-main/constant/setting.js @@ -0,0 +1,24 @@ +// 默认项目任务配置 +export const DEFAULT_SETTINGS = { + textSplitMinLength: 2500, + textSplitMaxLength: 4000, + questionGenerationLength: 240, + questionMaskRemovingProbability: 60, + huggingfaceToken: '', + concurrencyLimit: 5, + visionConcurrencyLimit: 5, + // 多轮对话数据集默认配置 + multiTurnSystemPrompt: '', + multiTurnScenario: '', + multiTurnRounds: 3, + multiTurnRoleA: '', + multiTurnRoleB: '', + // 测试集生成配置 + evalQuestionTypeRatios: { + true_false: 1, + single_choice: 1, + multiple_choice: 1, + short_answer: 1, + open_ended: 1 + } +}; diff --git a/easy-dataset-main/constant/sites.json b/easy-dataset-main/constant/sites.json new file mode 100644 index 0000000..f7907fc --- /dev/null +++ b/easy-dataset-main/constant/sites.json @@ -0,0 +1,286 @@ +[ + { + "name": "HuggingFace开源数据集", + "link": "https://huggingface.co/datasets", + "image": "/imgs/huggingface.png", + "description": "提供了丰富的开源数据集,涵盖多种领域和语言,支持自然语言处理、计算机视觉等多种任务。", + "labels": ["热门推荐", "多模态"] + }, + { + "name": "OpenDataLab开源数据集", + "link": "https://opendatalab.com/", + "image": "/imgs/opendatalab.png", + "description": "致力于收集和整理高质量的开源数据集,方便研究人员和开发者使用。", + "labels": ["热门推荐"] + }, + { + "name": "谷歌开源数据集", + "link": "https://datasetsearch.research.google.com", + "image": "/imgs/google.png", + "description": "谷歌提供的数据集搜索工具,可帮助用户找到来自不同来源的公开数据集。", + "labels": ["热门推荐", "英文资源"] + }, + { + "name": "kaggle开源数据集", + "link": "https://www.kaggle.com/datasets", + "image": "/imgs/kaggle.png", + "description": "Kaggle平台上的开源数据集,涉及各种领域和任务,常用于数据竞赛和实践。", + "labels": ["热门推荐", "英文资源"] + }, + { + "name": "ModelScope开源数据集", + "link": "https://modelscope.cn/datasets", + "image": "/imgs/modelscope.png", + "description": "提供了多种开源数据集,支持模型的训练和评估,涵盖多个领域。", + "labels": ["中文资源"] + }, + { + "name": "LUGE千言开源数据集", + "link": "https://www.luge.ai/", + "image": "/imgs/lluga.png", + "description": "专注于中文领域的开源数据集,包括自然语言处理、语音识别等方向。", + "labels": ["中文资源"] + }, + { + "name": "GitHub开源数据集", + "link": "https://github.com/awesomedata/awesome-public-datasets", + "image": "/imgs/github.png", + "description": "在GitHub上整理的优秀的公开数据集资源,涉及多个领域和方向。", + "labels": ["热门推荐"] + }, + { + "name": "AWS亚马逊开源数据集", + "link": "https://registry.opendata.aws/", + "image": "/imgs/aws.png", + "description": "提供了大量的公开数据集,涵盖多个领域,可在亚马逊云服务上直接访问和使用。", + "labels": ["英文资源"] + }, + { + "name": "TIANCHI天池开源数据集", + "link": "https://tianchi.aliyun.com/dataset/", + "description": "阿里云天池平台提供的开源数据集,涵盖多个领域的竞赛数据和公开数据。", + "labels": ["中文资源"] + }, + { + "name": "UCI开源数据集", + "link": "https://archive.ics.uci.edu/datasets", + "description": "加州大学欧文分校提供的开源数据集,涵盖多个领域,常用于机器学习研究。", + "labels": ["研究数据", "英文资源"] + }, + { + "name": "计算机视觉开源数据集", + "link": "https://visualdata.io/discovery", + "description": "专注于计算机视觉领域的开源数据集,支持相关模型的训练和评估。", + "labels": ["多模态"] + }, + { + "name": "BAAI开源数据集", + "link": "https://data.baai.ac.cn/data", + "description": "北京智源人工智能研究院提供的开源数据集,涵盖多个领域,支持大模型的训练。", + "labels": ["中文资源", "研究数据"] + }, + { + "name": "百度飞桨开源数据集", + "link": "https://aistudio.baidu.com/datasetoverview", + "description": "百度飞桨平台提供的开源数据集,支持深度学习模型的训练和评估。", + "labels": ["中文资源"] + }, + { + "name": "启智开源数据集", + "link": "https://openi.pcl.ac.cn/explore/datasets", + "description": "开源平台提供的多种开源数据集,涵盖多个领域,支持模型的训练和研究。", + "labels": ["中文资源"] + }, + { + "name": "LAION-2B-en", + "link": "https://laion.ai/", + "description": "包含25亿张图像和相应的文本描述,适用于多模态模型的训练。", + "labels": ["多模态"] + }, + { + "name": "Common Crawl", + "link": "https://commoncrawl.org/", + "description": "提供了大量的网页爬取数据,可用于语言模型的训练。", + "labels": ["英文资源", "研究数据"] + }, + { + "name": "The Pile", + "link": "https://github.com/EleutherAI/the-pile", + "description": "由多个数据集组成的大型语言模型训练数据集,涵盖多种文本类型。", + "labels": ["研究数据", "英文资源"] + }, + { + "name": "MuJoCo", + "link": "https://mujoco.org/", + "description": "用于物理模拟的机器人交互数据集,适用于强化学习和机器人控制任务。", + "labels": ["多模态"] + }, + { + "name": "Robotics Datasets", + "link": "https://roboticsdatasets.github.io/", + "description": "提供了多种机器人交互数据集,支持机器人学习和控制任务。", + "labels": ["多模态"] + }, + { + "name": "Atari Games", + "link": "https://www.atari.com/games", + "description": "经典的Atari游戏数据集,用于强化学习算法的基准测试。", + "labels": ["多模态"] + }, + { + "name": "Web-crawled Interactions", + "link": "https://commoncrawl.org/", + "description": "从网络平台上爬取的用户行为数据,适用于训练交互式代理。", + "labels": ["研究数据"] + }, + { + "name": "AI2 ARC Dataset", + "link": "https://allenai.org/data/arc", + "description": "用于评估AI常识推理和解决问题能力的多选题数据集。", + "labels": ["研究数据"] + }, + { + "name": "Speech Commands Dataset", + "link": "https://www.tensorflow.org/datasets/catalog/speech_commands", + "description": "包含数千个语音命令的音频数据集,适用于语音识别任务。", + "labels": ["多模态"] + }, + { + "name": "Environmental Audio Datasets", + "link": "https://www.tensorflow.org/datasets/catalog/audioset", + "description": "包含环境声音事件的音频数据集,适用于音频场景分类任务。", + "labels": ["多模态"] + }, + { + "name": "COVID-19 Open Research Dataset", + "link": "https://www.kaggle.com/allenai/cord-19-research-challenge", + "description": "包含45,000篇关于COVID-19的学术文章,适用于医疗AI研究。", + "labels": ["研究数据"] + }, + { + "name": "Waymo Open Dataset", + "link": "https://waymo.com/open/", + "description": "由Waymo发布的最多样化的自动驾驶数据集。", + "labels": ["多模态"] + }, + { + "name": "Labelme", + "link": "http://labelme.csail.mit.edu/Release3.0/", + "description": "包含大量标注图像的数据集,适用于计算机视觉任务。", + "labels": ["多模态"] + }, + { + "name": "Stanford Dogs Dataset", + "link": "http://vision.stanford.edu/aditya86/ImageNetDogs/", + "description": "包含20,500多张不同狗品种的图像数据集。", + "labels": ["多模态"] + }, + { + "name": "Flickr Audio Caption Corpus", + "link": "https://www.multispeech.org/2018/challenge.html", + "description": "包含超过40,000个口语描述的音频数据集。", + "labels": ["多模态"] + }, + { + "name": "Data.gov", + "link": "https://www.data.gov/", + "description": "美国政府开放数据平台,涵盖农业、气候、教育、能源等领域的公开数据集。", + "labels": ["政府数据", "英文资源"] + }, + { + "name": "Eurostat", + "link": "https://ec.europa.eu/eurostat", + "description": "欧盟统计局提供的经济、人口、社会等多领域统计数据。", + "labels": ["研究数据", "英文资源"] + }, + { + "name": "ImageNet", + "link": "https://www.image-net.org/", + "description": "大型图像数据集,包含数百万张标注图像,广泛用于计算机视觉任务。", + "labels": ["多模态", "计算机视觉"] + }, + { + "name": "COCO Dataset", + "link": "https://cocodataset.org/", + "description": "通用物体识别与分割数据集,适用于目标检测和图像分割任务。", + "labels": ["多模态"] + }, + { + "name": "World Bank Open Data", + "link": "https://data.worldbank.org/", + "description": "世界银行提供的全球经济指标、发展数据及统计报告。", + "labels": ["研究数据", "英文资源"] + }, + { + "name": "NASA Earth Data", + "link": "https://earthdata.nasa.gov/", + "description": "NASA地球科学数据,涵盖气候、地质、环境等领域的遥感数据。", + "labels": ["研究数据", "地球科学"] + }, + { + "name": "Yelp Open Dataset", + "link": "https://www.yelp.com/dataset", + "description": "包含商家信息、用户评论和图片数据,适用于商业分析和NLP任务。", + "labels": ["商业", "英文资源"] + }, + { + "name": "CIFAR-10/100", + "link": "https://www.cs.toronto.edu/~kriz/cifar.html", + "description": "经典的小规模图像分类数据集,包含10或100个类别的标注图像。", + "labels": ["多模态"] + }, + { + "name": "Global Health Observatory (WHO)", + "link": "https://www.who.int/data/gho", + "description": "世界卫生组织提供的全球公共卫生统计数据,包括疾病、营养等主题。", + "labels": ["医疗健康", "研究数据"] + }, + { + "name": "arXiv Dataset", + "link": "https://www.kaggle.com/Cornell-University/arxiv", + "description": "包含数百万篇arXiv学术论文的元数据和全文,适用于文本挖掘研究。", + "labels": ["研究数据", "英文资源"] + }, + { + "name": "LibriSpeech", + "link": "https://www.openslr.org/12", + "description": "包含1000小时英语语音数据,适用于语音识别模型训练。", + "labels": ["多模态", "语音识别"] + }, + { + "name": "KITTI Vision Benchmark", + "link": "http://www.cvlibs.net/datasets/kitti/", + "description": "自动驾驶领域经典数据集,包含立体视觉、激光雷达等多模态数据。", + "labels": ["多模态", "自动驾驶"] + }, + { + "name": "Cityscapes Dataset", + "link": "https://www.cityscapes-dataset.com/", + "description": "城市街景语义分割数据集,支持自动驾驶和计算机视觉研究。", + "labels": ["多模态"] + }, + { + "name": "CDC Data", + "link": "https://data.cdc.gov/", + "description": "美国疾病控制与预防中心发布的公共卫生数据集,涵盖疾病追踪和健康统计。", + "labels": ["医疗健康", "政府数据"] + }, + { + "name": "OpenStreetMap", + "link": "https://www.openstreetmap.org/", + "description": "开源地理数据协作项目,提供全球范围的道路、建筑等地理信息数据。", + "labels": ["地理信息", "众包数据"] + }, + { + "name": "FiveThirtyEight Datasets", + "link": "https://data.fivethirtyeight.com/", + "description": "涵盖政治、体育、文化等领域的数据集,常用于数据新闻分析。", + "labels": ["社会趋势", "英文资源"] + }, + { + "name": "Human Protein Atlas", + "link": "https://www.proteinatlas.org/", + "description": "包含人体蛋白质分布的组织图像数据,支持生物医学研究。", + "labels": ["医疗健康", "研究数据"] + } +] diff --git a/easy-dataset-main/docker-compose.yml b/easy-dataset-main/docker-compose.yml new file mode 100644 index 0000000..8417bd3 --- /dev/null +++ b/easy-dataset-main/docker-compose.yml @@ -0,0 +1,10 @@ +services: + easy-dataset: + image: ghcr.io/conardli/easy-dataset + container_name: easy-dataset + ports: + - '1717:1717' + volumes: + - ./local-db:/app/local-db + - ./prisma:/app/prisma + restart: unless-stopped diff --git a/easy-dataset-main/docker-entrypoint.sh b/easy-dataset-main/docker-entrypoint.sh new file mode 100644 index 0000000..610b939 --- /dev/null +++ b/easy-dataset-main/docker-entrypoint.sh @@ -0,0 +1,70 @@ +#!/bin/sh +set -e + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Define paths +PRISMA_DIR="/app/prisma" +PRISMA_TEMPLATE_DIR="/app/prisma-template" +DB_FILE="$PRISMA_DIR/db.sqlite" +LOCAL_DB_DIR="/app/local-db" + +echo "${GREEN}=== Easy Dataset Database Initialization ===${NC}" + +# Create prisma directory if it doesn't exist +if [ ! -d "$PRISMA_DIR" ]; then + echo "${YELLOW}Creating prisma directory...${NC}" + mkdir -p "$PRISMA_DIR" +fi + +# Check if database file exists +if [ ! -f "$DB_FILE" ]; then + echo "${YELLOW}Database file not found at: $DB_FILE${NC}" + + # Check if local-db has files (possible configuration issue) + if [ -d "$LOCAL_DB_DIR" ] && [ -n "$(ls -A $LOCAL_DB_DIR 2>/dev/null | grep -v 'empty.txt')" ]; then + echo "${YELLOW}Note: local-db contains files but database is missing.${NC}" + echo "${YELLOW}If you have existing data, ensure prisma volume is mounted.${NC}" + fi + + # Safety check: only initialize if directory is completely empty + if [ -z "$(ls -A $PRISMA_DIR 2>/dev/null)" ]; then + # Directory is completely empty - safe to initialize + echo "${GREEN}Prisma directory is empty. Initializing from template...${NC}" + + if [ -d "$PRISMA_TEMPLATE_DIR" ]; then + cp -r "$PRISMA_TEMPLATE_DIR"/* "$PRISMA_DIR/" + echo "${GREEN}Database initialized from template!${NC}" + else + echo "${YELLOW}No template found. Running prisma db push...${NC}" + cd /app + pnpm prisma db push --accept-data-loss + echo "${GREEN}Database created successfully!${NC}" + fi + else + # Directory is not empty but database is missing - error out + echo "${RED}ERROR: Database file missing but prisma directory is not empty!${NC}" + echo "${YELLOW}This may indicate a configuration problem.${NC}" + echo "" + echo "${YELLOW}Files in $PRISMA_DIR:${NC}" + ls -lh "$PRISMA_DIR" + echo "" + echo "${YELLOW}Please either:${NC}" + echo " 1. Remove all files in prisma directory to re-initialize" + echo " 2. Or run: pnpm prisma db push --accept-data-loss" + echo "" + exit 1 + fi +else + echo "${GREEN}Database file exists: $DB_FILE${NC}" +fi + +echo "${GREEN}=== Database Ready! Starting application... ===${NC}" +echo "" + +# Execute the command passed to the container +exec "$@" diff --git a/easy-dataset-main/electron/entitlements.mac.plist b/easy-dataset-main/electron/entitlements.mac.plist new file mode 100644 index 0000000..066f489 --- /dev/null +++ b/easy-dataset-main/electron/entitlements.mac.plist @@ -0,0 +1,16 @@ + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.network.client + + com.apple.security.files.user-selected.read-write + + + \ No newline at end of file diff --git a/easy-dataset-main/electron/loading.html b/easy-dataset-main/electron/loading.html new file mode 100644 index 0000000..fc5849d --- /dev/null +++ b/easy-dataset-main/electron/loading.html @@ -0,0 +1,124 @@ + + + + + + Easy Dataset Loading... + + + +
+ +

Easy Dataset

+

The first startup may take a bit longer to load. Please be patient. ...

+
+
+
+
+
+
+
+ + + + diff --git a/easy-dataset-main/electron/main.js b/easy-dataset-main/electron/main.js new file mode 100644 index 0000000..e114546 --- /dev/null +++ b/easy-dataset-main/electron/main.js @@ -0,0 +1,83 @@ +const { app, dialog, ipcMain } = require('electron'); +const { setupLogging, setupIpcLogging } = require('./modules/logger'); +const { createWindow, loadAppUrl, openDevTools, getMainWindow } = require('./modules/window-manager'); +const { createMenu } = require('./modules/menu'); +const { startNextServer } = require('./modules/server'); +const { setupAutoUpdater } = require('./modules/updater'); +const { initializeDatabase } = require('./modules/database'); +const { clearCache } = require('./modules/cache'); +const { setupIpcHandlers } = require('./modules/ipc-handlers'); + +// 是否是开发环境 +const isDev = process.env.NODE_ENV === 'development'; +const port = 1717; +let mainWindow; + +// 当 Electron 完成初始化时创建窗口 +app.whenReady().then(async () => { + try { + // 设置日志系统 + setupLogging(app); + + // 设置 IPC 处理程序 + setupIpcHandlers(app, isDev); + setupIpcLogging(ipcMain, app, isDev); + + // 初始化数据库 + await initializeDatabase(app); + + // 创建主窗口 + mainWindow = createWindow(isDev, port); + + // 创建菜单 + createMenu(mainWindow, () => clearCache(app)); + + // 在开发环境中加载 localhost URL + if (isDev) { + loadAppUrl(`http://localhost:${port}`); + openDevTools(); + } else { + // 在生产环境中启动 Next.js 服务 + const appUrl = await startNextServer(port, app); + loadAppUrl(appUrl); + } + + // 设置自动更新 + setupAutoUpdater(mainWindow); + + // 应用启动完成后的一段时间后自动检查更新 + setTimeout(() => { + if (!isDev) { + const { autoUpdater } = require('electron-updater'); + autoUpdater.checkForUpdates().catch(err => { + console.error('Automatic update check failed:', err); + }); + } + }, 10000); // Check for updates after 10 seconds + } catch (error) { + console.error('An error occurred during application initialization:', error); + dialog.showErrorBox( + 'Application Initialization Error', + `An error occurred during startup, which may affect application functionality. + Error details: ${error.message}` + ); + } +}); + +// 当所有窗口关闭时退出应用 +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') { + app.quit(); + } +}); + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + mainWindow = createWindow(isDev, port); + } +}); + +// 应用退出前清理 +app.on('before-quit', () => { + console.log('应用正在退出...'); +}); diff --git a/easy-dataset-main/electron/modules/cache.js b/easy-dataset-main/electron/modules/cache.js new file mode 100644 index 0000000..ed11100 --- /dev/null +++ b/easy-dataset-main/electron/modules/cache.js @@ -0,0 +1,21 @@ +const { clearLogs } = require('./logger'); +const { clearDatabaseCache } = require('./database'); + +/** + * 清除缓存函数 - 清理logs和local-db目录 + * @param {Object} app Electron app 对象 + * @returns {Promise} 操作是否成功 + */ +async function clearCache(app) { + // 清理日志目录 + await clearLogs(app); + + // 清理数据库缓存 + await clearDatabaseCache(app); + + return true; +} + +module.exports = { + clearCache +}; diff --git a/easy-dataset-main/electron/modules/database.js b/easy-dataset-main/electron/modules/database.js new file mode 100644 index 0000000..c8d9ad6 --- /dev/null +++ b/easy-dataset-main/electron/modules/database.js @@ -0,0 +1,147 @@ +const fs = require('fs'); +const path = require('path'); +const { dialog } = require('electron'); +const { updateDatabase } = require('./db-updater'); + +/** + * 清除数据库缓存 + * @param {Object} app Electron app 对象 + * @returns {Promise} 操作是否成功 + */ +async function clearDatabaseCache(app) { + // 清理local-db目录,保留db.sqlite文件 + const localDbDir = path.join(app.getPath('userData'), 'local-db'); + if (fs.existsSync(localDbDir)) { + // 读取目录下所有文件 + const files = await fs.promises.readdir(localDbDir); + // 删除除了db.sqlite之外的所有文件 + for (const file of files) { + if (file !== 'db.sqlite') { + const filePath = path.join(localDbDir, file); + const stat = await fs.promises.stat(filePath); + if (stat.isFile()) { + await fs.promises.unlink(filePath); + global.appLog(`已删除数据库缓存文件: ${filePath}`); + } else if (stat.isDirectory()) { + // 如果是目录,可能需要递归删除,根据需求决定 + global.appLog(`跳过目录: ${filePath}`); + } + } + } + } + return true; +} + +/** + * 初始化数据库 + * @param {Object} app Electron app 对象 + * @returns {Promise} 数据库配置信息 + */ +async function initializeDatabase(app) { + try { + // 设置数据库路径 + const userDataPath = app.getPath('userData'); + const dataDir = path.join(userDataPath, 'local-db'); + const dbFilePath = path.join(dataDir, 'db.sqlite'); + const dbJSONPath = path.join(dataDir, 'db.json'); + fs.writeFileSync(path.join(process.resourcesPath, 'root-path.txt'), dataDir); + + // 确保数据目录存在 + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + console.log(`数据目录已创建: ${dataDir}`); + } + + // 设置数据库连接字符串 (Prisma 格式) + const dbConnectionString = `file:${dbFilePath}`; + process.env.DATABASE_URL = dbConnectionString; + + // 仅在开发环境记录日志 + const logs = { + userDataPath, + dataDir, + dbFilePath, + dbConnectionString, + dbExists: fs.existsSync(dbFilePath) + }; + global.appLog(`数据库配置: ${JSON.stringify(logs)}`); + + if (!fs.existsSync(dbFilePath)) { + global.appLog('数据库文件不存在,正在初始化...'); + + try { + const resourcePath = + process.env.NODE_ENV === 'development' + ? path.join(__dirname, '../..', 'prisma', 'template.sqlite') + : path.join(process.resourcesPath, 'prisma', 'template.sqlite'); + + const resourceJSONPath = + process.env.NODE_ENV === 'development' + ? path.join(__dirname, '../..', 'prisma', 'sql.json') + : path.join(process.resourcesPath, 'prisma', 'sql.json'); + + global.appLog(`resourcePath: ${resourcePath}`); + + if (fs.existsSync(resourcePath)) { + fs.copyFileSync(resourcePath, dbFilePath); + global.appLog(`数据库已从模板初始化: ${dbFilePath}`); + } + + if (fs.existsSync(resourceJSONPath)) { + fs.copyFileSync(resourceJSONPath, dbJSONPath); + global.appLog(`数据库SQL配置已初始化: ${dbJSONPath}`); + } + } catch (error) { + console.error('数据库初始化失败:', error); + dialog.showErrorBox('数据库初始化失败', `应用无法初始化数据库,可能需要重新安装。\n错误详情: ${error.message}`); + throw error; + } + } else { + // 数据库文件存在,检查是否需要更新 + global.appLog('检查数据库是否需要更新...'); + try { + const resourcesPath = + process.env.NODE_ENV === 'development' ? path.join(__dirname, '../..') : process.resourcesPath; + + const isDev = process.env.NODE_ENV === 'development'; + + // 更新数据库 + const result = await updateDatabase(userDataPath, resourcesPath, isDev, global.appLog); + + if (result.updated) { + global.appLog(`数据库更新成功: ${result.message}`); + global.appLog(`执行的版本: ${result.executedVersions.join(', ')}`); + } else { + global.appLog(`数据库无需更新: ${result.message}`); + } + } catch (error) { + console.error('数据库更新失败:', error); + global.appLog(`数据库更新失败: ${error.message}`, 'error'); + + // 非致命错误,只提示但不阻止应用启动 + dialog.showMessageBox({ + type: 'warning', + title: '数据库更新警告', + message: '数据库更新过程中出现错误,部分功能可能受影响。', + detail: `错误详情: ${error.message}\n\n您可以继续使用应用,但如果遇到问题,请重新安装应用。`, + buttons: ['继续'] + }); + } + } + + return { + userDataPath, + dataDir, + dbFilePath, + dbConnectionString + }; + } catch (error) { + console.error('初始化数据库时发生错误:', error); + throw error; + } +} + +module.exports = { + clearDatabaseCache, + initializeDatabase +}; diff --git a/easy-dataset-main/electron/modules/db-updater.js b/easy-dataset-main/electron/modules/db-updater.js new file mode 100644 index 0000000..11702ea --- /dev/null +++ b/easy-dataset-main/electron/modules/db-updater.js @@ -0,0 +1,179 @@ +const fs = require('fs'); +const path = require('path'); +const { PrismaClient } = require('@prisma/client'); + +/** + * 执行SQL命令 + * @param {string} dbUrl 数据库连接 URL + * @param {string} sql SQL命令 + * @returns {Promise} + */ +async function executeSql(dbUrl, sql) { + // 允许多条SQL语句分开执行,支持分号和空行分隔 + const statements = sql + .split(';') + .map(stmt => stmt.trim()) + .filter(stmt => stmt.length > 0); + + if (statements.length === 0) { + return; + } + + // 设置环境变量 + process.env.DATABASE_URL = dbUrl; + + // 创建Prisma实例 + const prisma = new PrismaClient(); + + try { + // 执行每条SQL语句 + for (const statement of statements) { + await prisma.$executeRawUnsafe(statement); + } + } finally { + // 关闭连接 + await prisma.$disconnect(); + } +} + +/** + * 获取本地和应用的SQL配置文件 + * @param {string} userDataPath 用户数据目录 + * @param {string} resourcesPath 应用资源目录 + * @param {boolean} isDev 是否开发环境 + * @returns {Promise<{userSqlConfig: Array, appSqlConfig: Array}>} + */ +async function getSqlConfigs(userDataPath, resourcesPath, isDev, logger = console.log) { + // 用户SQL配置文件路径 + const userSqlPath = path.join(userDataPath, 'sql.json'); + + // 应用SQL配置文件路径 + const appSqlPath = isDev + ? path.join(__dirname, '..', 'prisma', 'sql.json') + : path.join(resourcesPath, 'prisma', 'sql.json'); + + let userSqlConfig = []; + let appSqlConfig = []; + + // 读取应用SQL配置 + try { + if (fs.existsSync(appSqlPath)) { + const appSqlContent = fs.readFileSync(appSqlPath, 'utf8'); + appSqlConfig = JSON.parse(appSqlContent); + } + } catch (error) { + throw new Error(`读取应用SQL配置文件失败: ${error.message}`); + } + + // 读取用户SQL配置(如果存在) + try { + if (fs.existsSync(userSqlPath)) { + const userSqlContent = fs.readFileSync(userSqlPath, 'utf8'); + userSqlConfig = JSON.parse(userSqlContent); + } + } catch (error) { + // 如果用户SQL配置不存在或无法解析,使用空数组 + userSqlConfig = []; + } + + logger(appSqlPath); + // logger(JSON.stringify(appSqlConfig, null, 2)); + logger(userSqlPath); + // logger(JSON.stringify(userSqlConfig, null, 2)); + + return { userSqlConfig, appSqlConfig }; +} + +/** + * 更新用户SQL配置文件 + * @param {string} userDataPath 用户数据目录 + * @param {Array} sqlConfig 新的SQL配置 + */ +function updateUserSqlConfig(userDataPath, sqlConfig) { + const userSqlPath = path.join(userDataPath, 'sql.json'); + fs.writeFileSync(userSqlPath, JSON.stringify(sqlConfig, null, 4), 'utf8'); +} + +// 不再需要版本比较功能 + +/** + * 获取需要执行的SQL命令 + * @param {Array} userSqlConfig 用户SQL配置 + * @param {Array} appSqlConfig 应用SQL配置 + * @returns {Array} 需要执行的SQL命令 + */ +function getSqlsToExecute(userSqlConfig, appSqlConfig) { + // 创建用户已执行的SQL集合 (使用 version + sql 的组合作为唯一标识) + const userExecutedSqlSet = new Set(); + userSqlConfig.forEach(item => { + const key = `${item.version}:${item.sql}`; + userExecutedSqlSet.add(key); + }); + + // 过滤出用户需要执行的SQL (即应用SQL配置中存在但用户尚未执行的SQL) + return appSqlConfig.filter(item => { + const key = `${item.version}:${item.sql}`; + return !userExecutedSqlSet.has(key); + }); +} + +/** + * 更新数据库 + * @param {string} userDataPath 用户数据目录 + * @param {string} resourcesPath 应用资源目录 + * @param {boolean} isDev 是否开发环境 + * @param {function} logger 日志函数 + */ +async function updateDatabase(userDataPath, resourcesPath, isDev, logger = console.log) { + const dbPath = path.join(userDataPath, 'local-db', 'db.sqlite'); + + try { + // 获取SQL配置 + const { userSqlConfig, appSqlConfig } = await getSqlConfigs(userDataPath, resourcesPath, isDev, logger); + + // 获取需要执行的SQL + const sqlsToExecute = getSqlsToExecute(userSqlConfig, appSqlConfig); + + if (sqlsToExecute.length === 0) { + logger('数据库已是最新版本,无需更新'); + return { updated: false, message: '数据库已是最新版本' }; + } + + // 设置数据库URL + const dbUrl = `file:${dbPath}`; + + // 执行SQL更新 + logger(`发现 ${sqlsToExecute.length} 个数据库更新,开始执行...`); + for (const item of sqlsToExecute) { + try { + logger(`执行版本 ${item.version} 的SQL更新: ${item.sql.substring(0, 100)}...`); + await executeSql(dbUrl, item.sql); + // 添加到用户SQL配置 + userSqlConfig.push(item); + } catch (error) { + logger(`执行版本 ${item.version} 的SQL更新失败: ${error.message}`); + } + } + + // 更新用户SQL配置文件 + updateUserSqlConfig(userDataPath, userSqlConfig); + + logger('数据库更新完成'); + return { + updated: true, + message: `成功执行了 ${sqlsToExecute.length} 个数据库更新`, + executedVersions: sqlsToExecute.map(item => item.version) + }; + } catch (error) { + logger(`数据库更新失败: ${error.message}`); + return { updated: false, error: error.message }; + } +} + +module.exports = { + updateDatabase, + executeSql, + getSqlConfigs, + updateUserSqlConfig, + getSqlsToExecute +}; diff --git a/easy-dataset-main/electron/modules/ipc-handlers.js b/easy-dataset-main/electron/modules/ipc-handlers.js new file mode 100644 index 0000000..8ec8007 --- /dev/null +++ b/easy-dataset-main/electron/modules/ipc-handlers.js @@ -0,0 +1,33 @@ +const { ipcMain } = require('electron'); +const { checkUpdate, downloadUpdate, installUpdate } = require('./updater'); + +/** + * 设置 IPC 处理程序 + * @param {Object} app Electron app 对象 + * @param {boolean} isDev 是否为开发环境 + */ +function setupIpcHandlers(app, isDev) { + // 获取用户数据路径 + ipcMain.on('get-user-data-path', event => { + event.returnValue = app.getPath('userData'); + }); + + // 检查更新 + ipcMain.handle('check-update', async () => { + return await checkUpdate(isDev); + }); + + // 下载更新 + ipcMain.handle('download-update', async () => { + return await downloadUpdate(); + }); + + // 安装更新 + ipcMain.handle('install-update', () => { + return installUpdate(); + }); +} + +module.exports = { + setupIpcHandlers +}; diff --git a/easy-dataset-main/electron/modules/logger.js b/easy-dataset-main/electron/modules/logger.js new file mode 100644 index 0000000..6da03fe --- /dev/null +++ b/easy-dataset-main/electron/modules/logger.js @@ -0,0 +1,84 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * 设置应用日志系统 + * @param {Object} app Electron app 对象 + * @returns {string} 日志文件路径 + */ +function setupLogging(app) { + const logDir = path.join(app.getPath('userData'), 'logs'); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + const logFilePath = path.join(logDir, `app-${new Date().toISOString().slice(0, 10)}.log`); + + // 创建自定义日志函数 + global.appLog = (message, level = 'info') => { + const timestamp = new Date().toISOString(); + const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`; + + // 同时输出到控制台和日志文件 + console.log(message); + fs.appendFileSync(logFilePath, logEntry); + }; + + // 捕获全局未处理异常并记录 + process.on('uncaughtException', error => { + global.appLog(`未捕获的异常: ${error.stack || error}`, 'error'); + }); + + return logFilePath; +} + +/** + * 设置 IPC 日志处理程序 + * @param {Object} ipcMain IPC 主进程对象 + * @param {Object} app Electron app 对象 + * @param {boolean} isDev 是否为开发环境 + */ +function setupIpcLogging(ipcMain, app, isDev) { + ipcMain.on('log', (event, { level, message }) => { + const timestamp = new Date().toISOString(); + const logEntry = `[${timestamp}] [${level.toUpperCase()}] ${message}\n`; + + // 只在客户端环境下写入文件 + if (!isDev || true) { + const logsDir = path.join(app.getPath('userData'), 'logs'); + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); + } + const logFile = path.join(logsDir, `${new Date().toISOString().split('T')[0]}.log`); + fs.appendFileSync(logFile, logEntry); + } + + // 同时输出到控制台 + console[level](message); + }); +} + +/** + * 清理日志文件 + * @param {Object} app Electron app 对象 + * @returns {Promise} + */ +async function clearLogs(app) { + const logsDir = path.join(app.getPath('userData'), 'logs'); + if (fs.existsSync(logsDir)) { + // 读取目录下所有文件 + const files = await fs.promises.readdir(logsDir); + // 删除所有文件 + for (const file of files) { + const filePath = path.join(logsDir, file); + await fs.promises.unlink(filePath); + global.appLog(`已删除日志文件: ${filePath}`); + } + } +} + +module.exports = { + setupLogging, + setupIpcLogging, + clearLogs +}; diff --git a/easy-dataset-main/electron/modules/menu.js b/easy-dataset-main/electron/modules/menu.js new file mode 100644 index 0000000..0ee9cec --- /dev/null +++ b/easy-dataset-main/electron/modules/menu.js @@ -0,0 +1,136 @@ +const { Menu, dialog, shell, app } = require('electron'); +const path = require('path'); +const fs = require('fs'); +const os = require('os'); +const { getAppVersion } = require('../util'); + +/** + * 创建应用菜单 + * @param {BrowserWindow} mainWindow 主窗口 + * @param {Function} clearCache 清除缓存函数 + */ +function createMenu(mainWindow, clearCache) { + const template = [ + { + label: 'File', + submenu: [{ role: 'quit', label: 'Quit' }] + }, + { + label: 'Edit', + submenu: [ + { role: 'undo', label: 'Undo' }, + { role: 'redo', label: 'Redo' }, + { type: 'separator' }, + { role: 'cut', label: 'Cut' }, + { role: 'copy', label: 'Copy' }, + { role: 'paste', label: 'Paste' } + ] + }, + { + label: 'View', + submenu: [ + { role: 'reload', label: 'Refresh' }, + { type: 'separator' }, + { role: 'resetzoom', label: 'Reset Zoom' }, + { role: 'zoomin', label: 'Zoom In' }, + { role: 'zoomout', label: 'Zoom Out' }, + { type: 'separator' }, + { role: 'togglefullscreen', label: 'Fullscreen' } + ] + }, + { + label: 'Help', + submenu: [ + { + label: 'About', + click: () => { + dialog.showMessageBox(mainWindow, { + title: 'About Easy Dataset', + message: `Easy Dataset v${getAppVersion()}`, + detail: 'An application for creating fine-tuning datasets for large models.', + buttons: ['OK'] + }); + } + }, + { + label: 'Visit GitHub', + click: () => { + shell.openExternal('https://github.com/ConardLi/easy-dataset'); + } + } + ] + }, + { + label: 'More', + submenu: [ + { role: 'toggledevtools', label: 'Developer Tools' }, + { + label: 'Open Logs Directory', + click: () => { + const logsDir = path.join(app.getPath('userData'), 'logs'); + if (!fs.existsSync(logsDir)) { + fs.mkdirSync(logsDir, { recursive: true }); + } + shell.openPath(logsDir); + } + }, + { + label: 'Open Data Directory', + click: () => { + const dataDir = path.join(app.getPath('userData'), 'local-db'); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + shell.openPath(dataDir); + } + }, + { + label: 'Open Data Directory (History)', + click: () => { + const dataDir = path.join(os.homedir(), '.easy-dataset-db'); + if (!fs.existsSync(dataDir)) { + fs.mkdirSync(dataDir, { recursive: true }); + } + shell.openPath(dataDir); + } + }, + { + label: 'Clear Cache', + click: async () => { + try { + const response = await dialog.showMessageBox(mainWindow, { + type: 'question', + buttons: ['Cancel', 'Confirm'], + defaultId: 1, + title: 'Clear Cache', + message: 'Are you sure you want to clear the cache?', + detail: + 'This will delete all files in the logs directory and local database cache files (excluding main database files).' + }); + + if (response.response === 1) { + // User clicked confirm + await clearCache(); + dialog.showMessageBox(mainWindow, { + type: 'info', + title: 'Cleared Successfully', + message: 'Cache has been cleared successfully' + }); + } + } catch (error) { + global.appLog(`Failed to clear cache: ${error.message}`, 'error'); + dialog.showErrorBox('Failed to clear cache', error.message); + } + } + } + ] + } + ]; + + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +} + +module.exports = { + createMenu +}; diff --git a/easy-dataset-main/electron/modules/server.js b/easy-dataset-main/electron/modules/server.js new file mode 100644 index 0000000..a0051bf --- /dev/null +++ b/easy-dataset-main/electron/modules/server.js @@ -0,0 +1,118 @@ +const http = require('http'); +const path = require('path'); +const fs = require('fs'); +const { dialog } = require('electron'); + +/** + * 检查端口是否被占用 + * @param {number} port 端口号 + * @returns {Promise} 端口是否被占用 + */ +function checkPort(port) { + return new Promise(resolve => { + const server = http.createServer(); + server.once('error', () => { + resolve(true); // 端口被占用 + }); + server.once('listening', () => { + server.close(); + resolve(false); // 端口未被占用 + }); + server.listen(port); + }); +} + +/** + * 启动 Next.js 服务 + * @param {number} port 端口号 + * @param {Object} app Electron app 对象 + * @returns {Promise} 服务URL + */ +async function startNextServer(port, app) { + console.log(`Easy Dataset 客户端启动中,当前版本: ${require('../util').getAppVersion()}`); + + // 设置日志文件路径 + const logDir = path.join(app.getPath('userData'), 'logs'); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + const logFile = path.join(logDir, `nextjs-${new Date().toISOString().replace(/:/g, '-')}.log`); + const logStream = fs.createWriteStream(logFile, { flags: 'a' }); + + // 重定向 console.log 和 console.error + const originalConsoleLog = console.log; + const originalConsoleError = console.error; + + console.log = function () { + const args = Array.from(arguments); + const logMessage = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg)).join(' '); + + logStream.write(`[${new Date().toISOString()}] [LOG] ${logMessage}\n`); + originalConsoleLog.apply(console, args); + }; + + console.error = function () { + const args = Array.from(arguments); + const logMessage = args.map(arg => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg)).join(' '); + + logStream.write(`[${new Date().toISOString()}] [ERROR] ${logMessage}\n`); + originalConsoleError.apply(console, args); + }; + + // 检查端口是否被占用 + const isPortBusy = await checkPort(port); + if (isPortBusy) { + console.log(`端口 ${port} 已被占用,尝试直接连接...`); + return `http://localhost:${port}`; + } + + console.log(`启动 Next.js 服务,端口: ${port}`); + + try { + // 动态导入 Next.js + const next = require('next'); + const nextApp = next({ + dev: false, + dir: path.join(__dirname, '../..'), + conf: { + // 配置 Next.js 的日志输出 + onInfo: info => { + console.log(`[Next.js Info] ${info}`); + }, + onError: error => { + console.error(`[Next.js Error] ${error}`); + }, + onWarn: warn => { + console.log(`[Next.js Warning] ${warn}`); + } + } + }); + const handle = nextApp.getRequestHandler(); + + await nextApp.prepare(); + + const server = http.createServer((req, res) => { + // 记录请求日志 + console.log(`[Request] ${req.method} ${req.url}`); + handle(req, res); + }); + + return new Promise(resolve => { + server.listen(port, err => { + if (err) throw err; + console.log(`服务已启动,正在打开应用...`); + resolve(`http://localhost:${port}`); + }); + }); + } catch (error) { + console.error('启动服务失败:', error); + dialog.showErrorBox('启动失败', `无法启动 Next.js 服务: ${error.message}`); + app.quit(); + return ''; + } +} + +module.exports = { + checkPort, + startNextServer +}; diff --git a/easy-dataset-main/electron/modules/updater.js b/easy-dataset-main/electron/modules/updater.js new file mode 100644 index 0000000..eeff538 --- /dev/null +++ b/easy-dataset-main/electron/modules/updater.js @@ -0,0 +1,116 @@ +const { autoUpdater } = require('electron-updater'); +const { getAppVersion } = require('../util'); + +/** + * 设置自动更新 + * @param {BrowserWindow} mainWindow 主窗口 + */ +function setupAutoUpdater(mainWindow) { + autoUpdater.autoDownload = false; + autoUpdater.allowDowngrade = false; + + // 检查更新时出错 + autoUpdater.on('error', error => { + if (mainWindow) { + mainWindow.webContents.send('update-error', error.message); + } + }); + + // 检查到更新时 + autoUpdater.on('update-available', info => { + if (mainWindow) { + mainWindow.webContents.send('update-available', { + version: info.version, + releaseDate: info.releaseDate, + releaseNotes: info.releaseNotes + }); + } + }); + + // 没有可用更新 + autoUpdater.on('update-not-available', () => { + if (mainWindow) { + mainWindow.webContents.send('update-not-available'); + } + }); + + // 下载进度 + autoUpdater.on('download-progress', progressObj => { + if (mainWindow) { + mainWindow.webContents.send('download-progress', progressObj); + } + }); + + // 下载完成 + autoUpdater.on('update-downloaded', info => { + if (mainWindow) { + mainWindow.webContents.send('update-downloaded', { + version: info.version, + releaseDate: info.releaseDate, + releaseNotes: info.releaseNotes + }); + } + }); +} + +/** + * 检查更新 + * @param {boolean} isDev 是否为开发环境 + * @returns {Promise} 更新信息 + */ +async function checkUpdate(isDev) { + try { + if (isDev) { + // 开发环境下模拟更新检查 + return { + hasUpdate: false, + currentVersion: getAppVersion(), + message: '开发环境下不检查更新' + }; + } + + // 返回当前版本信息,并开始检查更新 + const result = await autoUpdater.checkForUpdates(); + return { + checking: true, + currentVersion: getAppVersion() + }; + } catch (error) { + console.error('检查更新失败:', error); + return { + hasUpdate: false, + currentVersion: getAppVersion(), + error: error.message + }; + } +} + +/** + * 下载更新 + * @returns {Promise} 下载状态 + */ +async function downloadUpdate() { + try { + autoUpdater.downloadUpdate(); + return { downloading: true }; + } catch (error) { + console.error('下载更新失败:', error); + return { error: error.message }; + } +} + +/** + * 安装更新 + * @returns {Object} 安装状态 + */ +function installUpdate() { + autoUpdater.quitAndInstall(false, true); + return { installing: true }; +} + +module.exports = { + setupAutoUpdater, + checkUpdate, + downloadUpdate, + installUpdate +}; diff --git a/easy-dataset-main/electron/modules/window-manager.js b/easy-dataset-main/electron/modules/window-manager.js new file mode 100644 index 0000000..fc01329 --- /dev/null +++ b/easy-dataset-main/electron/modules/window-manager.js @@ -0,0 +1,113 @@ +const { BrowserWindow, shell } = require('electron'); +const path = require('path'); +const url = require('url'); +const { getAppVersion } = require('../util'); + +let mainWindow; + +/** + * 创建主窗口 + * @param {boolean} isDev 是否为开发环境 + * @param {number} port 服务端口 + * @returns {BrowserWindow} 创建的主窗口 + */ +function createWindow(isDev, port) { + mainWindow = new BrowserWindow({ + width: 1200, + height: 800, + show: false, + frame: true, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, '..', 'preload.js') + }, + icon: path.join(__dirname, '../../public/imgs/logo.ico') + }); + + // 设置窗口标题 + mainWindow.setTitle(`Easy Dataset v${getAppVersion()}`); + const loadingPath = url.format({ + pathname: path.join(__dirname, '..', 'loading.html'), + protocol: 'file:', + slashes: true + }); + + // 加载 loading 页面时使用专门的 preload 脚本 + mainWindow.webContents.on('did-finish-load', () => { + mainWindow.show(); + }); + + mainWindow.loadURL(loadingPath); + + // 处理窗口导航事件,将外部链接在浏览器中打开 + mainWindow.webContents.on('will-navigate', (event, navigationUrl) => { + // 解析当前 URL 和导航 URL + const parsedUrl = new URL(navigationUrl); + const currentHostname = isDev ? 'localhost' : 'localhost'; + const currentPort = port.toString(); + + // 检查是否是外部链接 + if (parsedUrl.hostname !== currentHostname || (parsedUrl.port !== currentPort && parsedUrl.port !== '')) { + event.preventDefault(); + shell.openExternal(navigationUrl); + } + }); + + // 处理新窗口打开请求,将外部链接在浏览器中打开 + mainWindow.webContents.setWindowOpenHandler(({ url: navigationUrl }) => { + // 解析导航 URL + const parsedUrl = new URL(navigationUrl); + const currentHostname = isDev ? 'localhost' : 'localhost'; + const currentPort = port.toString(); + + // 检查是否是外部链接 + if (parsedUrl.hostname !== currentHostname || (parsedUrl.port !== currentPort && parsedUrl.port !== '')) { + shell.openExternal(navigationUrl); + return { action: 'deny' }; + } + return { action: 'allow' }; + }); + + mainWindow.on('closed', () => { + mainWindow = null; + }); + + mainWindow.maximize(); + + return mainWindow; +} + +/** + * 加载应用URL + * @param {string} appUrl 应用URL + */ +function loadAppUrl(appUrl) { + if (mainWindow) { + mainWindow.loadURL(appUrl); + } +} + +/** + * 在开发环境中打开开发者工具 + */ +function openDevTools() { + if (mainWindow) { + mainWindow.webContents.openDevTools(); + } +} + +/** + * 获取主窗口 + * @returns {BrowserWindow} 主窗口 + */ +function getMainWindow() { + return mainWindow; +} + +module.exports = { + createWindow, + loadAppUrl, + openDevTools, + getMainWindow +}; diff --git a/easy-dataset-main/electron/preload.js b/easy-dataset-main/electron/preload.js new file mode 100644 index 0000000..db62abb --- /dev/null +++ b/easy-dataset-main/electron/preload.js @@ -0,0 +1,73 @@ +const { contextBridge, ipcRenderer } = require('electron'); + +// 在渲染进程中暴露安全的 API +contextBridge.exposeInMainWorld('electron', { + // 获取应用版本 + getAppVersion: () => ipcRenderer.invoke('get-app-version'), + + // 获取当前语言 + getLanguage: () => { + // 尝试从本地存储获取语言设置 + const storedLang = localStorage.getItem('i18nextLng'); + // 如果存在则返回,否则返回系统语言或默认为中文 + return storedLang || navigator.language.startsWith('zh') ? 'zh' : 'en'; + }, + + // 获取用户数据目录 + getUserDataPath: () => { + try { + return ipcRenderer.sendSync('get-user-data-path'); + } catch (error) { + console.error('获取用户数据目录失败:', error); + return null; + } + }, + + // 更新相关 API + updater: { + // 检查更新 + checkForUpdates: () => ipcRenderer.invoke('check-update'), + + // 下载更新 + downloadUpdate: () => ipcRenderer.invoke('download-update'), + + // 安装更新 + installUpdate: () => ipcRenderer.invoke('install-update'), + + // 监听更新事件 + onUpdateAvailable: callback => { + const handler = (_, info) => callback(info); + ipcRenderer.on('update-available', handler); + return () => ipcRenderer.removeListener('update-available', handler); + }, + + onUpdateNotAvailable: callback => { + const handler = () => callback(); + ipcRenderer.on('update-not-available', handler); + return () => ipcRenderer.removeListener('update-not-available', handler); + }, + + onUpdateError: callback => { + const handler = (_, error) => callback(error); + ipcRenderer.on('update-error', handler); + return () => ipcRenderer.removeListener('update-error', handler); + }, + + onDownloadProgress: callback => { + const handler = (_, progress) => callback(progress); + ipcRenderer.on('download-progress', handler); + return () => ipcRenderer.removeListener('download-progress', handler); + }, + + onUpdateDownloaded: callback => { + const handler = (_, info) => callback(info); + ipcRenderer.on('update-downloaded', handler); + return () => ipcRenderer.removeListener('update-downloaded', handler); + } + } +}); + +// 通知渲染进程 preload 脚本已加载完成 +window.addEventListener('DOMContentLoaded', () => { + console.log('Electron preload script loaded'); +}); diff --git a/easy-dataset-main/electron/util.js b/easy-dataset-main/electron/util.js new file mode 100644 index 0000000..83288cb --- /dev/null +++ b/easy-dataset-main/electron/util.js @@ -0,0 +1,19 @@ +const path = require('path'); +const fs = require('fs'); + +// 获取应用版本 +const getAppVersion = () => { + try { + const packageJsonPath = path.join(__dirname, '../package.json'); + if (fs.existsSync(packageJsonPath)) { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); + return packageJson.version; + } + return '1.0.0'; + } catch (error) { + console.error('读取版本信息失败:', error); + return '1.0.0'; + } +}; + +module.exports = { getAppVersion }; diff --git a/easy-dataset-main/hooks/useDebounce.js b/easy-dataset-main/hooks/useDebounce.js new file mode 100644 index 0000000..38c81c5 --- /dev/null +++ b/easy-dataset-main/hooks/useDebounce.js @@ -0,0 +1,14 @@ +import { useEffect, useState } from 'react'; + +export function useDebounce(value, delay = 500) { + const [debouncedValue, setDebouncedValue] = useState(value); + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedValue(value); + }, delay); + return () => { + clearTimeout(timer); + }; + }, [value, delay]); + return debouncedValue; +} diff --git a/easy-dataset-main/hooks/useFileProcessingStatus.js b/easy-dataset-main/hooks/useFileProcessingStatus.js new file mode 100644 index 0000000..e9fc49f --- /dev/null +++ b/easy-dataset-main/hooks/useFileProcessingStatus.js @@ -0,0 +1,57 @@ +import { useState, useEffect } from 'react'; + +// 存储文件处理状态的共享对象 +const fileProcessingSubscribers = { + value: false, + listeners: new Set() +}; + +// 存储文件任务信息的共享对象 +const fileTaskSubscribers = { + value: null, + listeners: new Set() +}; + +/** + * 自定义hook,用于在组件间共享文件处理任务的状态 + */ +export default function useFileProcessingStatus() { + const [taskFileProcessing, setTaskFileProcessing] = useState(fileProcessingSubscribers.value); + const [task, setTask] = useState(fileTaskSubscribers.value); + + useEffect(() => { + // 添加当前组件为订阅者 + const updateProcessingState = newValue => setTaskFileProcessing(newValue); + const updateTaskState = newTask => setTask(newTask); + + fileProcessingSubscribers.listeners.add(updateProcessingState); + fileTaskSubscribers.listeners.add(updateTaskState); + + // 组件卸载时清理 + return () => { + fileProcessingSubscribers.listeners.delete(updateProcessingState); + fileTaskSubscribers.listeners.delete(updateTaskState); + }; + }, []); + + // 共享的setState函数 + const setSharedFileProcessing = newValue => { + fileProcessingSubscribers.value = newValue; + // 通知所有订阅者 + fileProcessingSubscribers.listeners.forEach(listener => listener(newValue)); + }; + + // 共享的setTask函数 + const setSharedTask = newTask => { + fileTaskSubscribers.value = newTask; + // 通知所有订阅者 + fileTaskSubscribers.listeners.forEach(listener => listener(newTask)); + }; + + return { + taskFileProcessing, + task, + setTaskFileProcessing: setSharedFileProcessing, + setTask: setSharedTask + }; +} diff --git a/easy-dataset-main/hooks/useGenerateDataset.js b/easy-dataset-main/hooks/useGenerateDataset.js new file mode 100644 index 0000000..792be94 --- /dev/null +++ b/easy-dataset-main/hooks/useGenerateDataset.js @@ -0,0 +1,135 @@ +import { useCallback } from 'react'; +import { toast } from 'sonner'; +import i18n from '@/lib/i18n'; +import axios from 'axios'; +import { useAtomValue } from 'jotai/index'; +import { selectedModelInfoAtom } from '@/lib/store'; +import { useTranslation } from 'react-i18next'; + +export function useGenerateDataset() { + const model = useAtomValue(selectedModelInfoAtom); + const { t } = useTranslation(); + + const generateSingleDataset = useCallback( + async ({ projectId, questionId, questionInfo, imageId, imageName }) => { + // 获取模型参数 + if (!model) { + toast.error(t('models.configNotFound')); + return null; + } + + // 判断是否为图片问题 + const isImageQuestion = !!imageId; + + // 调用API生成数据集 + const currentLanguage = i18n.language === 'zh-CN' ? '中文' : 'en'; + + if (isImageQuestion) { + // 图片问题:调用图片数据集生成接口 + toast.promise( + axios.post(`/api/projects/${projectId}/images/datasets`, { + imageName, + question: { question: questionInfo, id: questionId }, + model, + language: currentLanguage + }), + { + loading: t('datasets.generating'), + description: `图片:【${imageName}】\n问题:【${questionInfo}】`, + position: 'top-right', + success: data => { + return '生成数据集成功'; + }, + error: error => { + return t('datasets.generateFailed', { error: error.response?.data?.error }); + } + } + ); + } else { + // 文本问题:调用普通数据集生成接口 + toast.promise( + axios.post(`/api/projects/${projectId}/datasets`, { + questionId, + model, + language: currentLanguage + }), + { + loading: t('datasets.generating'), + description: `问题:【${questionInfo}】`, + position: 'top-right', + success: data => { + return '生成数据集成功'; + }, + error: error => { + return t('datasets.generateFailed', { error: error.response?.data?.error }); + } + } + ); + } + }, + [model, t] + ); + + const generateMultipleDataset = useCallback( + async (projectId, questions) => { + let completed = 0; + const total = questions.length; + // 显示带进度的Loading + const loadingToastId = toast.loading(`正在处理请求 (${completed}/${total})...`, { position: 'top-right' }); + + // 处理每个请求 + const processRequest = async question => { + try { + const isImageQuestion = !!question.imageId; + let response; + + if (isImageQuestion) { + // 图片问题 + response = await axios.post(`/api/projects/${projectId}/images/datasets`, { + imageName: question.imageName, + question, + model, + language: i18n.language === 'zh-CN' ? '中文' : 'en' + }); + } else { + // 文本问题 + response = await axios.post(`/api/projects/${projectId}/datasets`, { + questionId: question.id, + model, + language: i18n.language === 'zh-CN' ? '中文' : 'en' + }); + } + + const data = response.data; + completed++; + toast.success(`${question.question} 完成`, { position: 'top-right' }); + toast.loading(`正在处理请求 (${completed}/${total})...`, { id: loadingToastId }); + return data; + } catch (error) { + completed++; + toast.error(`${question.question} 失败`, { + description: error.message, + position: 'top-right' + }); + toast.loading(`正在处理请求 (${completed}/${total})...`, { id: loadingToastId }); + throw error; + } + }; + + try { + const results = await Promise.allSettled(questions.map(req => processRequest(req))); + // 全部完成后更新Loading为完成状态 + toast.success(`全部请求处理完成 (成功: ${results.filter(r => r.status === 'fulfilled').length}/${total})`, { + id: loadingToastId, + position: 'top-right' + }); + return results; + } catch { + // Promise.allSettled不会进入catch,这里只是保险 + } + }, + [model, t] + ); + + return { generateSingleDataset, generateMultipleDataset }; +} diff --git a/easy-dataset-main/hooks/useModelPlayground.js b/easy-dataset-main/hooks/useModelPlayground.js new file mode 100644 index 0000000..5b53108 --- /dev/null +++ b/easy-dataset-main/hooks/useModelPlayground.js @@ -0,0 +1,406 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useAtomValue } from 'jotai/index'; +import { modelConfigListAtom } from '@/lib/store'; + +export default function useModelPlayground(projectId, defaultModelId = null) { + // 状态管理 + const [selectedModels, setSelectedModels] = useState(defaultModelId ? [defaultModelId] : []); + const [loading, setLoading] = useState({}); + const [userInput, setUserInput] = useState(''); + const [conversations, setConversations] = useState({}); + const [error, setError] = useState(null); + const [outputMode, setOutputMode] = useState('normal'); // 'normal' 或 'streaming' + const [uploadedImage, setUploadedImage] = useState(null); // 存储上传的图片Base64 + + const availableModels = useAtomValue(modelConfigListAtom); + + // 初始化会话状态 + useEffect(() => { + if (selectedModels.length > 0) { + const initialConversations = {}; + selectedModels.forEach(modelId => { + if (!conversations[modelId]) { + initialConversations[modelId] = []; + } + }); + + if (Object.keys(initialConversations).length > 0) { + setConversations(prev => ({ + ...prev, + ...initialConversations + })); + } + } + }, [selectedModels]); + + // 处理模型选择 + const handleModelSelection = event => { + const { + target: { value } + } = event; + + // 限制最多选择 3 个模型 + const selectedValues = typeof value === 'string' ? value.split(',') : value; + const limitedSelection = selectedValues.slice(0, 3); + + setSelectedModels(limitedSelection); + }; + + // 处理用户输入 + const handleInputChange = e => { + setUserInput(e.target.value); + }; + + // 处理图片上传 + const handleImageUpload = e => { + const file = e.target.files[0]; + if (file) { + const reader = new FileReader(); + reader.onloadend = () => { + setUploadedImage(reader.result); + }; + reader.readAsDataURL(file); + } + }; + + // 删除已上传的图片 + const handleRemoveImage = () => { + setUploadedImage(null); + }; + + // 处理输出模式切换 + const handleOutputModeChange = event => { + setOutputMode(event.target.value); + }; + + // 发送消息给所有选中的模型 + const handleSendMessage = async () => { + if (!userInput.trim() || Object.values(loading).some(value => value) || selectedModels.length === 0) return; + + // 获取用户输入 + const input = userInput.trim(); + setUserInput(''); + + // 获取图片(如果有的话) + const image = uploadedImage; + setUploadedImage(null); // 清除图片 + + // 更新所有选中模型的对话 + const updatedConversations = { ...conversations }; + selectedModels.forEach(modelId => { + if (!updatedConversations[modelId]) { + updatedConversations[modelId] = []; + } + // 检查是否有图片并且当前模型是视觉模型 + const model = availableModels.find(m => m.id === modelId); + const isVisionModel = model && model.type === 'vision'; + + if (isVisionModel && image) { + // 如果是视觉模型并且有图片,使用复合格式 + updatedConversations[modelId].push({ + role: 'user', + content: [ + { type: 'text', text: input || '请描述这个图片' }, + { type: 'image_url', image_url: { url: image } } + ] + }); + } else { + // 其他情况使用纯文本 + updatedConversations[modelId].push({ + role: 'user', + content: input + }); + } + }); + + setConversations(updatedConversations); + + // 为每个模型设置独立的加载状态 + const updatedLoading = {}; + selectedModels.forEach(modelId => { + updatedLoading[modelId] = true; + }); + setLoading(updatedLoading); + + // 为每个模型单独发送请求 + selectedModels.forEach(async modelId => { + const model = availableModels.find(m => m.id === modelId); + if (!model) { + // 模型配置不存在 + const modelConversation = [...(updatedConversations[modelId] || [])]; + + // 更新对话状态 + setConversations(prev => ({ + ...prev, + [modelId]: [...modelConversation, { role: 'error', content: '模型配置不存在' }] + })); + + // 更新加载状态 + setLoading(prev => ({ ...prev, [modelId]: false })); + return; + } + + try { + // 检查是否是视觉模型且有图片 + const isVisionModel = model.type === 'vision'; + + // 构建请求消息 + let requestMessages = [...updatedConversations[modelId]]; // 复制当前消息历史 + + // 如果是vision模型并且有图片,将最后一条用户消息替换为包含图片的消息 + if (isVisionModel && image && requestMessages.length > 0) { + // 找到最后一条用户消息 + const lastUserMsgIndex = requestMessages.length - 1; + // 替换为包含图片的消息 + requestMessages[lastUserMsgIndex] = { + role: 'user', + content: [ + { type: 'text', text: input || '请描述这个图片' }, + { type: 'image_url', image_url: { url: image } } + ] + }; + } + + // 根据输出模式选择不同的处理方式 + if (outputMode === 'streaming') { + // 流式输出处理 + // 先添加一个空的助手回复,用于后续流式更新 + setConversations(prev => { + const modelConversation = [...(prev[modelId] || [])]; + return { + ...prev, + [modelId]: [ + ...modelConversation, + { + role: 'assistant', + content: '', + isStreaming: true, + thinking: '', // 添加推理过程字段 + showThinking: true // 默认显示推理过程 + } + ] + }; + }); + + const response = await fetch(`/api/projects/${projectId}/playground/chat/stream`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: model, + messages: requestMessages + }) + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder('utf-8'); + let accumulatedContent = ''; + + // 状态变量,用于跟踪是否正在处理思维链 + let isInThinking = false; + let currentThinking = ''; + let currentContent = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + // 解码收到的数据块 + const chunk = decoder.decode(value, { stream: true }); + + // 处理当前数据块 + for (let i = 0; i < chunk.length; i++) { + const char = chunk[i]; + + // 检测开始标签 + if (i + 6 <= chunk.length && chunk.substring(i, i + 7) === '') { + isInThinking = true; + i += 6; // 跳过标签 + continue; + } + + // 检测结束标签 + if (i + 7 <= chunk.length && chunk.substring(i, i + 8) === '') { + isInThinking = false; + i += 7; // 跳过标签 + continue; + } + + // 根据当前状态添加到对应内容中 + if (isInThinking) { + currentThinking += char; + } else { + currentContent += char; + } + } + + // 累积全部内容以便最终处理 + accumulatedContent += chunk; + + // 更新对话内容 + setConversations(prev => { + const modelConversation = [...prev[modelId]]; + const lastIndex = modelConversation.length - 1; + + // 更新最后一条消息的内容,包括思维链 + modelConversation[lastIndex] = { + ...modelConversation[lastIndex], + content: currentContent, + thinking: currentThinking, + showThinking: currentThinking.length > 0 // 只要有思维链内容就显示 + }; + + return { + ...prev, + [modelId]: modelConversation + }; + }); + } + + // 完成流式传输,移除流式标记 + // 使用刚刚实时跟踪的 currentThinking 和 currentContent作为最终的思维链和内容 + let finalThinking = currentThinking; + let finalAnswer = currentContent; + + // 如果到流结束时还在思维链中,确保解析完整的思维链内容 + if (isInThinking) { + console.log('警告: 流结束时仍在思维链中,可能有标签不完整'); + isInThinking = false; + } + + setConversations(prev => { + const modelConversation = [...prev[modelId]]; + const lastIndex = modelConversation.length - 1; + + // 更新最后一条消息,移除流式标记 + modelConversation[lastIndex] = { + role: 'assistant', + content: finalAnswer, + thinking: finalThinking, + showThinking: finalThinking ? true : false, + isStreaming: false + }; + + return { + ...prev, + [modelId]: modelConversation + }; + }); + } else { + // 普通输出处理 + const response = await fetch(`/api/projects/${projectId}/playground/chat`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: { + ...model, + extra_body: { enable_thinking: true } // 启用思考链 + }, + messages: requestMessages + }) + }); + + // 获取响应数据 + const data = await response.json(); + + // 独立更新此模型的对话状态 + setConversations(prev => { + const modelConversation = [...(prev[modelId] || [])]; + + if (response.ok) { + // 处理可能包含思考链的内容 + let thinking = ''; + let content = data.response; + + // 检查是否包含思考链 + if (content && content.includes('')) { + const thinkParts = content.split(/(.*?)<\/think>/s); + if (thinkParts.length >= 3) { + thinking = thinkParts[1] || ''; + // 移除思考链部分,只保留最终回答 + content = thinkParts.filter((_, i) => i % 2 === 0).join(''); + } + } + + return { + ...prev, + [modelId]: [ + ...modelConversation, + { + role: 'assistant', + content: content, + thinking: thinking, + showThinking: thinking ? true : false + } + ] + }; + } else { + return { + ...prev, + [modelId]: [...modelConversation, { role: 'error', content: `错误: ${data.error || '请求失败'}` }] + }; + } + }); + } + } catch (error) { + console.error(`请求模型 ${model.name} 失败:`, error); + + // 独立更新此模型的对话状态 - 添加错误消息 + setConversations(prev => { + const modelConversation = [...(prev[modelId] || [])]; + return { + ...prev, + [modelId]: [...modelConversation, { role: 'error', content: `错误: ${error.message}` }] + }; + }); + } finally { + // 更新此模型的加载状态 + setLoading(prev => ({ ...prev, [modelId]: false })); + } + }); + }; + + // 清空所有对话 + const handleClearConversations = () => { + const clearedConversations = {}; + selectedModels.forEach(modelId => { + clearedConversations[modelId] = []; + }); + setConversations(clearedConversations); + setLoading({}); + }; + + // 获取模型名称 + const getModelName = modelId => { + const model = availableModels.find(m => m.id === modelId); + return model ? `${model.provider}: ${model.name}` : modelId; + }; + + return { + availableModels, + selectedModels, + loading, + userInput, + conversations, + error, + outputMode, + uploadedImage, + handleModelSelection, + handleInputChange, + handleImageUpload, + handleRemoveImage, + handleSendMessage, + handleClearConversations, + handleOutputModeChange, + getModelName + }; +} diff --git a/easy-dataset-main/hooks/useSnackbar.js b/easy-dataset-main/hooks/useSnackbar.js new file mode 100644 index 0000000..8651975 --- /dev/null +++ b/easy-dataset-main/hooks/useSnackbar.js @@ -0,0 +1,73 @@ +'use client'; + +import { useState, useCallback } from 'react'; +import { Snackbar, Alert } from '@mui/material'; + +export const useSnackbar = () => { + const [open, setOpen] = useState(false); + const [message, setMessage] = useState(''); + const [severity, setSeverity] = useState('info'); + + const showMessage = useCallback((newMessage, newSeverity = 'info') => { + setMessage(newMessage); + setSeverity(newSeverity); + setOpen(true); + }, []); + + const showSuccess = useCallback( + message => { + showMessage(message, 'success'); + }, + [showMessage] + ); + + const showError = useCallback( + message => { + showMessage(message, 'error'); + }, + [showMessage] + ); + + const showInfo = useCallback( + message => { + showMessage(message, 'info'); + }, + [showMessage] + ); + + const showWarning = useCallback( + message => { + showMessage(message, 'warning'); + }, + [showMessage] + ); + + const handleClose = useCallback(() => { + setOpen(false); + }, []); + + const SnackbarComponent = useCallback( + () => ( + + + {message} + + + ), + [open, message, severity, handleClose] + ); + + return { + showMessage, + showSuccess, + showError, + showInfo, + showWarning, + SnackbarComponent + }; +}; diff --git a/easy-dataset-main/hooks/useTaskSettings.js b/easy-dataset-main/hooks/useTaskSettings.js new file mode 100644 index 0000000..338b161 --- /dev/null +++ b/easy-dataset-main/hooks/useTaskSettings.js @@ -0,0 +1,63 @@ +import { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { DEFAULT_SETTINGS } from '@/constant/setting'; + +export default function useTaskSettings(projectId) { + const { t } = useTranslation(); + const [taskSettings, setTaskSettings] = useState({ + ...DEFAULT_SETTINGS + }); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + const [success, setSuccess] = useState(false); + + useEffect(() => { + async function fetchTaskSettings() { + try { + setLoading(true); + const response = await fetch(`/api/projects/${projectId}/tasks`); + if (!response.ok) { + throw new Error(t('settings.fetchTasksFailed')); + } + + const data = await response.json(); + + // 如果没有配置,使用默认值 + if (Object.keys(data).length === 0) { + setTaskSettings({ + ...DEFAULT_SETTINGS + }); + } else { + // 确保所有默认值都被正确设置,特别是数字类型的字段 + const mergedSettings = { + ...DEFAULT_SETTINGS, + ...data + }; + + // 确保 multiTurnRounds 是数字类型 + if (mergedSettings.multiTurnRounds !== undefined) { + mergedSettings.multiTurnRounds = Number(mergedSettings.multiTurnRounds); + } + + setTaskSettings(mergedSettings); + } + } catch (error) { + console.error('获取任务配置出错:', error); + setError(error.message); + } finally { + setLoading(false); + } + } + + fetchTaskSettings(); + }, [projectId, t]); + + return { + taskSettings, + setTaskSettings, + loading, + error, + success, + setSuccess + }; +} diff --git a/easy-dataset-main/jsconfig.json b/easy-dataset-main/jsconfig.json new file mode 100644 index 0000000..9c33383 --- /dev/null +++ b/easy-dataset-main/jsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./*"] + } + } +} diff --git a/easy-dataset-main/locales/en/translation.json b/easy-dataset-main/locales/en/translation.json new file mode 100644 index 0000000..33e7eed --- /dev/null +++ b/easy-dataset-main/locales/en/translation.json @@ -0,0 +1,1871 @@ +{ + "language": { + "switchToEnglish": "Switch to English", + "switchToChinese": "Switch to Chinese", + "switcherTitle": "Change Language / 切换语言 / Dil Değiştir", + "english": "English", + "chineseSimplified": "Simplified Chinese", + "turkish": "Turkish", + "portugues": "Portuguese", + "en": "EN", + "zh": "中" + }, + "theme": { + "switchToLight": "Switch to Light Mode", + "switchToDark": "Switch to Dark Mode" + }, + "settings": { + "promptConfig": "Prompt Configuration", + "promptsDescription": "Configure prompt used in the project, supporting global prompts and scenario-specific prompts.", + "globalPrompt": "Global Prompt", + "questionPrompt": "Question Generation Prompt", + "answerPrompt": "Answer Generation Prompt", + "labelPrompt": "Question Labeling Prompt", + "domainTreePrompt": "Domain Tree Building Prompt", + "globalPromptPlaceholder": "Enter global prompt that will serve as the base prompt for all scenarios", + "questionPromptPlaceholder": "Enter prompt for generating questions", + "answerPromptPlaceholder": "Enter prompt for generating answers", + "labelPromptPlaceholder": "Enter prompt for question labeling (not supported currently)", + "domainTreePromptPlaceholder": "Enter prompt for building domain tree", + "cleanPrompt": "Data Cleaning Prompt", + "cleanPromptPlaceholder": "Enter custom prompt for data cleaning", + "loadPromptsFailed": "Failed to load prompt configurations", + "savePromptsSuccess": "Successfully saved prompt configurations", + "savePromptsFailed": "Failed to save prompt configurations", + "title": "Settings", + "basicInfo": "Basic Info", + "modelConfig": "Model Configuration", + "taskConfig": "Task Configuration", + "tabsAriaLabel": "Settings Tabs", + "idNotEditable": "Project ID is not editable", + "saveBasicInfo": "Save Basic Info", + "saveSuccess": "Save Successful", + "saveFailed": "Save Failed", + "deleteSuccess": "Delete Successful", + "deleteFailed": "Delete Failed", + "fetchTasksFailed": "Failed to fetch task settings", + "saveTasksFailed": "Failed to save task settings", + "textSplitSettings": "Text Split Settings", + "minLength": "Minimum Length", + "maxLength": "Maximum Split Length", + "textSplitDescription": "Adjust the text split length range", + "splitType": "Split Strategy", + "splitTypeMarkdown": "Document Structure Spliting (Markdown)", + "splitTypeMarkdownDesc": "Automatically split the text according to the titles in the document, maintaining semantic integrity. It is suitable for Markdown documents with a clear structure.", + "splitTypeRecursive": "Text Structure Spliting (Custom Delimiter)", + "splitTypeRecursiveDesc": "Recursively attempt multiple levels of delimiters (configurable). First, use delimiters with higher priority, and then use secondary delimiters. It is suitable for complex documents.", + "splitTypeText": "Fixed-length Spliting (Characters)", + "splitTypeTextDesc": "Split the text according to the specified delimiter (configurable), and then combine it according to the specified length. It is suitable for ordinary text files.", + "splitTypeToken": "Fixed-length Spliting (Tokens)", + "splitTypeTokenDesc": "Block based on the number of Tokens (not the number of characters).", + "splitTypeCode": "Intelligent Spliting of Program Code", + "splitTypeCodeDesc": "Intelligently block according to the syntax structure of different programming languages, avoiding splitting at places with incomplete syntax.", + "splitTypeCustom": "Custom Symbol Splitting", + "splitTypeCustomDesc": "Split documents based on custom symbols. The separator will be discarded and the split text chunks will not be affected by chunk size.", + "codeLanguage": "Programming Language", + "codeLanguageHelper": "Select the programming language for smarter code splitting based on language syntax.", + "chunkSize": "Chunk Size", + "chunkOverlap": "Chunk Overlap", + "separator": "Separator", + "separatorHelper": "Separator used for splitting text, e.g. \n\n for blank lines", + "customSeparator": "Custom Separator", + "customSeparatorHelper": "Custom separator used for splitting text, e.g. --- or ===", + "separators": "Separators List", + "separatorsInput": "Separators (comma separated)", + "separatorsHelper": "Comma-separated list of separators in priority order", + "questionGenSettings": "Question Generation Settings", + "questionGenLength": "Question Generation Length: {{length}}", + "questionMaskRemovingProbability": "Removing Question Marks Probability: {{probability}}%", + "questionGenDescription": "Set the maximum length for generated questions", + "huggingfaceSettings": "Hugging Face Settings", + "datasetUpload": "Dataset Upload Settings", + "huggingfaceToken": "Hugging Face Token", + "huggingfaceNotImplemented": "", + "concurrencyLimit": "Concurrency Limit", + "concurrencyLimitHelper": "Limit the number of tasks for generating questions and generating datasets simultaneously. ", + "saveTaskConfig": "Save Task Config", + "pdfSettings": "PDF file conversion configuration", + "minerUToken": "MinerU Token configuration", + "minerUHelper": "MinerU Token is valid for only 14 days. Please replace the Token in time", + "minerULocalUrl": "PDF Conversion (MinerU Local) URL Configuration", + "vision": "Custom large-scale vision model configuration", + "visionConcurrencyLimit": "Concurrency limit for custom large-scale vision models", + "prompts": { + "selectPromptFirst": "Please select a prompt on the left", + "customized": "Customized", + "editPrompt": "Edit Prompt", + "restoreDefault": "Restore Default", + "promptType": "Prompt Type", + "keyName": "Key Name", + "contentPlaceholder": "Please enter custom prompt content...", + "restoreDefaultContent": "Restore Default Content", + "noPromptsAvailable": "No prompts available", + "restoreSuccess": "Successfully restored to default prompt", + "restoreFailed": "Failed to restore default prompt", + "deleteError": "Error deleting prompt:", + "saveSuccess": "Prompt saved successfully", + "saveFailed": "Failed to save prompt", + "saveError": "Error saving prompt:", + "createCustomPrompt": "Create Custom Prompt", + "fetchContentError": "Failed to fetch latest prompt content:" + }, + "multiTurnSettings": "Multi-turn Conversation Settings", + "multiTurnSystemPrompt": "System Prompt", + "multiTurnSystemPromptHelper": "System prompt for multi-turn conversation generation", + "multiTurnScenario": "Conversation Scenario", + "multiTurnScenarioHelper": "Describe the conversation scenario or context", + "multiTurnRounds": "Number of Rounds", + "multiTurnRoleA": "Role A", + "multiTurnRoleAHelper": "Description of the first participant in the conversation", + "multiTurnRoleB": "Role B", + "multiTurnRoleBHelper": "Description of the second participant in the conversation", + "multiTurnDescription": "Multi-turn conversation generation configuration", + "evalQuestionSettings": "Evaluation Test Set Generation Settings", + "evalQuestionSettingsDescription": "Configure the ratio of each question type when generating test sets. A ratio of 0 means this type will not be generated", + "evalTrueFalseRatio": "True/False Ratio", + "evalSingleChoiceRatio": "Single Choice Ratio", + "evalMultipleChoiceRatio": "Multiple Choice Ratio", + "evalShortAnswerRatio": "Short Answer Ratio", + "evalOpenEndedRatio": "Open-ended Ratio", + "evalQuestionRatioHelper": "The system will automatically allocate the number of questions for each type based on the set ratios. The sum of all ratios does not need to equal a specific value" + }, + "questions": { + "autoGenerateDataset": "Auto Generate Dataset", + "autoGenerateDatasetTip": "Create background batch processing tasks: automatically query text blocks pending question generation and extract questions.", + "filterAll": "All Questions", + "filterAnswered": "With Answers", + "filterUnanswered": "Without Answers", + "filterChunkNamePlaceholder": "Filter by chunk name...", + "sourceTypeAll": "All Sources", + "sourceTypeText": "Text Source", + "sourceTypeImage": "Image Source", + "title": "Questions", + "confirmDeleteTitle": "Confirm Delete Question", + "confirmDeleteContent": "Are you sure you want to delete the question \"{{question}}\"? This action cannot be undone.", + "deleting": "Deleting question...", + "batchDeleteTitle": "Confirm Batch Delete", + "batchDeleting": "Deleting {{count}} questions...", + "deleteSuccess": "Question deleted successfully", + "deleteFailed": "Failed to delete question", + "batchDeleteSuccess": "Successfully deleted {{count}} questions", + "batchDeletePartial": "Delete completed, success: {{success}}, failed: {{failed}}", + "batchDeleteFailed": "Failed to batch delete questions", + "noQuestionsSelected": "Please select questions first", + "batchGenerateStart": "Starting to generate datasets for {{count}} questions", + "invalidQuestionKey": "Invalid question key", + "listView": "Question List", + "treeView": "Domain Tree", + "selectAll": "Select All", + "selectedCount": "Selected {{count}} questions", + "totalCount": "Total {{count}} questions", + "searchPlaceholder": "Search questions or tags...", + "searchMatch": "Match", + "searchNotMatch": "Not Match", + "deleteSelected": "Delete Selected", + "batchGenerate": "Batch Generate Datasets", + "generating": "Generating Dataset", + "generatingProgress": "Completed: {{completed}}/{{total}}", + "generatedCount": "Generated {{count}} datasets", + "pleaseWait": "Please wait...", + "selectAllLimitReached": "Selected {{count}} questions (maximum limit reached)", + "selectAllFailed": "Select all operation failed, please try again later", + "createSuccess": "Question created successfully", + "updateSuccess": "Question updated successfully", + "operationSuccess": "Operation successful", + "operationFailed": "Operation failed", + "editQuestion": "Edit Question", + "questionContent": "Question Content", + "sourceType": "Data Source Type", + "sourceType.text": "Text", + "sourceType.image": "Image", + "selectChunk": "Select Text Chunk", + "searchChunk": "Search text chunks...", + "selectImage": "Select Image", + "searchImage": "Search images...", + "selectTag": "Select Tag", + "searchTag": "Search tags...", + "createQuestion": "Create Question", + "createNormalQuestion": "Create Normal Question", + "createQuestionTemplate": "Create Question Template", + "questionPlaceholder": "Please enter your question", + "noChunkSelected": "Please select a text chunk first", + "noTagSelected": "Please select a tag", + "fetchTemplatesFailed": "Failed to fetch question templates", + "createTemplateSuccess": "Question template created successfully", + "createTemplateFailed": "Failed to create question template", + "updateTemplateSuccess": "Question template updated successfully", + "updateTemplateFailed": "Failed to update question template", + "deleteTemplateSuccess": "Question template deleted successfully", + "deleteTemplateFailed": "Failed to delete question template", + "exportQuestions": "Export Questions", + "exportScope": "Export Scope", + "exportAll": "Export All ({{count}} questions)", + "exportSelected": "Export Selected ({{count}} questions)", + "exportFormat": "Export Format", + "txtFormat": "Plain Text (Questions Only)", + "exportSuccess": "Questions exported successfully", + "exportFailed": "Failed to export questions", + "template": { + "management": "Question Templates", + "create": "Create Template", + "edit": "Edit Template", + "question": "Question Content", + "description": "Prompt", + "descriptionHelp": "Used to be included in the overall prompt when AI generates answers related to this question template, to influence the final answer generation results", + "noTemplates": "No question templates yet, click create button to add", + "deleteConfirm": "Are you sure you want to delete this question template?", + "used": "Used", + "addLabel": "Add Label", + "customFormat": "Custom Format", + "customFormatHelp": "Enter JSON format output constraint", + "customFormatInfo": "This format will be provided to the LLM as a prompt to constrain output format", + "sourceTypeInfo": "Data Source Type", + "sourceType": { + "label": "Data Source Type", + "image": "Image", + "text": "Text" + }, + "answerType": { + "label": "Answer Type", + "text": "Text", + "tags": "Tags", + "customFormat": "Custom Format" + }, + "errors": { + "questionRequired": "Please enter question content", + "labelsRequired": "Label type questions require at least one label", + "customFormatRequired": "Please enter custom format", + "invalidJson": "Invalid JSON format" + }, + "autoGenerate": "Auto-generate questions after creating template", + "autoGenerateHelpText": "Will automatically create questions based on this template for all text chunks in the project", + "autoGenerateHelpImage": "Will automatically create questions based on this template for all images in the project", + "confirmAutoGenerate": "Confirm Auto-generate Questions", + "confirmAutoGenerateTextMessage": "You have chosen to auto-generate questions. The system will create questions based on this template for all text chunks in the project.", + "confirmAutoGenerateImageMessage": "You have chosen to auto-generate questions. The system will create questions based on this template for all images in the project.", + "autoGenerateWarning": "This operation may create a large number of questions. Please confirm to continue.", + "autoGenerateSuccess": "Successfully created questions for {{count}} data sources", + "autoGeneratePartialFail": "Successfully created {{success}} questions, {{fail}} failed", + "autoGenerateFailed": "Failed to auto-generate questions" + }, + "generateSingleTurnDataset": "Generate Single-turn Dataset", + "generateSingleTurnDatasetDesc": "Generate Q&A dataset based on questions", + "generateMultiTurnDataset": "Generate Multi-Turn Dataset", + "generateImageDataset": "Generate Image Q&A Dataset", + "generateMultiTurnDatasetDesc": "Generate multi-turn conversation dataset based on questions", + "deleteConfirm": "Are you sure you want to delete this question? This action cannot be undone." + }, + "common": { + "dataSource": "Data Source", + "menu": "Menu", + "openMenu": "Open navigation menu", + "all": "All", + "jumpTo": "Jump To", + "unknownError": "Unknown Error", + "create": "Create", + "edit": "Edit", + "delete": "Delete", + "save": "Save", + "cancel": "Cancel", + "confirm": "Confirm", + "complete": "Complete", + "close": "Close", + "add": "Add", + "remove": "Remove", + "loading": "Loading...", + "yes": "Yes", + "no": "No", + "confirmDelete": "Confirm Delete", + "saving": "Saving...", + "deleting": "Deleting...", + "actions": "Actions", + "confirmDeleteDataSet": "Are you sure you want to delete this dataset? This action cannot be undone.", + "noData": "None", + "failed": "Failed", + "success": "Success", + "backToList": "Back to List", + "label": "Label", + "confirmDeleteDescription": "Are you sure you want to delete this File? This action cannot be undone.", + "more": "More", + "import": "Import", + "export": "Export", + "fetchError": "Fetching data failed", + "confirmDeleteQuestion": "Are you sure you want to delete this question? This action cannot be undone.", + "deleteSuccess": "Delete successful", + "visitGitHub": "Visit GitHub Repository", + "syncOldData": "Sync Old Data", + "copy": "Copy", + "enabled": "Enabled", + "disabled": "Disabled", + "copied": "Copied", + "generating": "Generating...", + "processing": "Processing...", + "items": "items", + "detailInfo": "Detail Info", + "reset": "Reset", + "apply": "Apply", + "mainNavigation": "Main Navigation", + "goHome": "Go to Home", + "goToHomePage": "Go to Home Page", + "mobileNavigation": "Mobile Navigation Menu", + "navigation": "Navigation", + "closeMenu": "Close Menu", + "documentation": "Documentation", + "viewOnGitHub": "View on GitHub", + "back": "Back", + "refresh": "Refresh", + "expand": "Expand All", + "collapse": "Collapse" + }, + "home": { + "title": "Easy Dataset", + "subtitle": "A powerful tool for creating fine-tuning datasets for Large Language Models", + "createProject": "Create Project", + "searchDataset": "Search Public Datasets" + }, + "projects": { + "reuseConfig": "Reuse Model Config", + "noReuse": "No Configuration Reuse", + "selectProject": "Select Project", + "fetchFailed": "Failed to fetch project list", + "fetchError": "Error fetching project list", + "loading": "Loading your projects...", + "createFailed": "Failed to create project", + "createError": "Error creating project", + "createNew": "Create New Project", + "saveFailed": "Failed to save project", + "id": "Project ID", + "name": "Project Name", + "description": "Project Description", + "questions": "Questions", + "datasets": "Datasets", + "evalDatasets": "Eval Datasets", + "tokens": "Tokens", + "lastUpdated": "Last Updated", + "viewDetails": "View Details", + "createFirst": "Please create a project first", + "noProjects": "No projects found", + "notExist": "The project does not exist.", + "createProject": "Create Project", + "deleteConfirm": "Are you sure you want to delete this project? This action cannot be undone.", + "deleteSuccess": "Project deleted successfully", + "deleteFailed": "Failed to delete project", + "backToHome": "Back to Home", + "deleteConfirmTitle": "Confirm Delete" + }, + "textSplit": { + "dragToUpload": "Drag files to upload", + "fileList": "File List", + "autoGenerateQuestions": "Auto Generate", + "autoGenerateQuestionsTip": "Create background batch processing tasks: automatically query text blocks pending question generation and extract questions.", + "exportChunks": "Export Chunks", + "allChunks": "All Text Chunks", + "generatedQuestions2": "With Questions", + "ungeneratedQuestions": "Without Questions", + "contentKeyword": "Text Chunk Content", + "contentKeywordPlaceholder": "Enter keywords to search chunk content", + "characterRange": "Character Range", + "questionStatus": "Question Status", + "noFilesUploaded": "No files uploaded yet", + "unknownFile": "Unknown File", + "fetchFilesFailed": "Failed to fetch files", + "editTag": "Edit Tag", + "deleteTag": "Delete Tag", + "addTag": "Add Tag", + "selectedCount": "Selected {{count}} text chunks", + "totalCount": "total {{count}} text chunks", + "batchGenerateQuestions": "Batch Generate", + "batchDeleteChunks": "Batch Delete", + "batchDeleteChunksConfirmTitle": "Confirm Batch Delete", + "batchDeleteChunksConfirmMessage": "Are you sure you want to delete the selected {{count}} text chunks? This action cannot be undone.", + "uploadedDocuments": "Uploaded {{count}} Documents", + "title": "Texts", + "uploadNewDocument": "Upload New Document", + "selectFile": "Select File", + "markdownOnly": "Currently only supports Markdown (.md) format files", + "supportedFormats": "Supported formats: .pdf .md, .txt, .docx", + "uploadAndProcess": "Upload and Process", + "selectedFiles": "Selected Files ({{count}})", + "oneFileMessage": "File upload not allowed, please delete the existing file first", + "mutilFileMessage": "After uploading a new file, the domain tree will be rebuilt", + "noChunks": " No text chunks found", + "chunkDetails": "Chunk Details: {{chunkId}}", + "fetchChunksFailed": "Failed to fetch text chunks", + "fetchChunksError": "Error fetching text chunks", + "fileResultReceived": "File result received", + "fileUploadSuccess": "File uploaded successfully", + "splitTextFailed": "Text splitting failed", + "splitTextError": "Error splitting text", + "deleteChunkFailed": "Failed to delete text chunk", + "deleteChunkError": "Error deleting text chunk", + "selectModelFirst": "Please select a model first, you can select from the top navigation bar", + "modelNotAvailable": "Selected model is not available, please select again", + "generateQuestionsFailed": "Failed to generate questions for chunk {{chunkId}}", + "questionsGenerated": "{{total}} questions generated", + "customSplitMode": "Custom Split Mode", + "customSplitInstructions": "Select text to add split points. The system will place split markers at your selected positions.", + "splitPointsList": "Added Split Points", + "saveSplitPoints": "Save Split Points", + "confirmCustomSplitTitle": "Confirm Split Replacement", + "confirmCustomSplitMessage": "Note: Custom split points will replace the previously automated split results for this document. Do you want to continue?", + "customSplitSuccess": "Custom split saved successfully", + "customSplitFailed": "Failed to save custom split", + "missingRequiredData": "Missing required data", + "chunksPreview": "Chunks Size Preview", + "chunk": "Chunk", + "characters": " chars", + "questionsGeneratedSuccess": "Successfully generated {{total}} questions for the text chunk", + "generateQuestionsForChunkFailed": "Failed to generate questions for chunk {{chunkId}}", + "generateQuestionsForChunkError": "Error generating questions for chunk {{chunkId}}", + "generateQuestionsError": "Error generating questions", + "partialSuccess": "Partially successful question generation ({{successCount}}/{{total}}), {{errorCount}} chunks failed", + "allSuccess": "Successfully generated {{totalQuestions}} questions for {{successCount}} text chunks", + "fileDeleted": "File {{fileName}} deleted, refreshing text chunk list", + "tabs": { + "smartSplit": "Smart Split", + "domainAnalysis": "Domain Analysis" + }, + "loading": "Loading...", + "fetchingDocuments": "Fetching document data", + "processing": "Processing...", + "progressStatus": "Selected {{total}} text chunks, {{completed}} completed", + "processingPleaseWait": "Processing, please wait!", + "oneFileLimit": "File upload not allowed, there is already an uploaded file", + "unsupportedFormat": "Unsupported file format: {{files}}", + "modelInfoParseError": "Failed to parse model information", + "uploadFailed": "Upload failed", + "uploadSuccess": "Successfully uploaded {{count}} files", + "deleteFailed": "Failed to delete file", + "deleteSuccess": "File {{fileName}} has been successfully deleted", + "generatedQuestions": "{{count}} Questions", + "generatedEvalQuestions": "{{count}} Test Questions", + "viewDetails": "View Details", + "generateQuestions": "Generate Questions", + "generateEvalQuestions": "Generate Test Set", + "evalQuestionsGeneratedSuccess": "Successfully generated {{total}} evaluation questions", + "generateEvalQuestionsFailed": "Failed to generate evaluation questions", + "dataCleaning": "Data Cleaning", + "batchDataCleaning": "Batch Data Cleaning", + "autoDataCleaning": "Auto Data Cleaning", + "autoDataCleaningTip": "Create background batch processing task: automatically clean all text chunks", + "autoEvalGeneration": "Auto Generate Eval Dataset", + "autoEvalGenerationTip": "Create background batch processing task: automatically generate evaluation datasets for all text chunks without eval questions", + "autoTasks": "Auto Tasks", + "dataCleaningSuccess": "Data cleaning completed, original length: {{originalLength}}, cleaned length: {{cleanedLength}}", + "dataCleaningFailed": "Data cleaning failed for text chunk {{chunkId}}", + "dataCleaningForChunkSuccess": "Data cleaning completed for text chunk {{chunkId}}", + "dataCleaningForChunkFailed": "Data cleaning failed for text chunk {{chunkId}}", + "dataCleaningForChunkError": "Data cleaning error for text chunk {{chunkId}}", + "dataCleaningPartialSuccess": "Partial data cleaning success ({{successCount}}/{{total}}), {{errorCount}} text chunks failed", + "dataCleaningAllSuccess": "Successfully completed data cleaning for {{successCount}} text chunks", + "charsCount": "Characters", + "pdfProcessStatus": "Total {{total}} files, {{completed}} have been converted", + "pdfPageProcessStatus": "Processing {{fileName}}, {{completed}} out of {{total}} pages converted", + "pdfProcessing": "Converting file...", + "pdfProcessingFailed": "File conversion failed!", + "pdfProcess": "File detected!", + "selectPdfProcessingStrategy": "Please select the file processing method:", + "pdfProcessingStrategyDefault": "Default", + "pdfProcessingStrategyDefaultHelper": "Use the built-in PDF parsing strategy", + "pdfProcessingStrategyMinerUHelper": "Use MinerU API for parsing. Please configure the MinerU API Token first", + "pdfProcessingStrategyVision": "Custom Vision Model", + "pdfProcessingStrategyVisionHelper": "Use a custom vision model for parsing", + "pdfProcessingToast": "File detected. The system will create a background task to parse the file", + "pdfProcessingWaring": "There is a file processing task in progress. It is recommended to wait for the task to complete before performing other operations, otherwise it may affect the quality of data generation!", + "pdfProcessingLoading": "Executing file conversion task, please wait for the task to complete before uploading new files...", + "basicPdfParsing": "Basic PDF Parsing", + "basicPdfParsingDesc": "Capable of identifying the key outlines of simple PDF files with high speed", + "mineruApiDesc": "Capable of identifying complex PDF files, including formulas and charts (Requires configuration of MinerU API Key)", + "mineruLocalDesc": "Capable of identifying complex PDF files, including formulas and charts (requires configuring MinerU Local URL)", + "mineruApiDescDisabled": "Please go to Project Settings - Task Configuration to set up MinerU Token", + "mineruLocalDisabled": "Please first set up MinerU Local URL in [Project Settings - Task Configuration]", + "mineruWebPlatform": "MinerU Online Platform Parsing", + "mineruWebPlatformDesc": "Capable of identifying complex PDF files, including formulas and charts (Requires redirecting to another website)", + "mineruSelected": "Selected to use MinerU for parsing PDFs", + "mineruLocalSelected": "Selected to use MinerU Local for parsing PDFs", + "customVisionModel": "Custom Vision Model Parsing", + "customVisionModelDesc": "Capable of identifying complex PDF files, including formulas and charts (Requires adding vision model configuration to the model configuration)", + "customVisionModelSelected": "Selected to use the visual large model {{name}} ({{provider}}) for parsing PDFs", + "defaultSelected": "Selected to use the default built-in strategy for parsing PDFs", + "download": "Download the document", + "deleteFile": "Delete document", + "batchDelete": "Batch Delete ({{count}})", + "batchDeleteTitle": "Batch Delete Files", + "batchDeleteConfirm": "Are you sure you want to delete the selected {{count}} files? This action cannot be undone.", + "batchDeleteSuccess": "Successfully deleted {{count}} files", + "batchDeleteFailed": "Batch delete failed", + "searchFiles": "Search files...", + "searchResults": "Found {{count}} files ({{total}} total)", + "noSearchResults": "No files found containing \"{{searchTerm}}\"", + "noResultsOnCurrentPage": "No search results on current page, please return to the first page", + "noDataOnCurrentPage": "No data on current page", + "viewChunk": "View Text Chunk", + "editChunk": "Edit Text Chunk {{chunkId}}", + "editChunkSuccess": "Text chunk edited successfully", + "editChunkFailed": "Failed to edit text chunk", + "editChunkError": "Error occurred when editing text chunk", + "deleteFileWarning": "Warning: Deleting this document will also delete the following related items", + "deleteFileWarningChunks": "All associated text chunks", + "deleteFileWarningQuestions": "All questions generated from these chunks", + "deleteFileWarningDatasets": "All datasets created from these questions", + "domainTree": { + "firstUploadTitle": "Domain Tree Generation", + "uploadTitle": "Document Upload - Domain Tree Processing", + "deleteTitle": "Document Deletion - Domain Tree Processing", + "reviseOption": "Revise Domain Tree", + "reviseDesc": "Modify the current domain tree based on added or deleted documents, only affecting changed parts", + "rebuildOption": "Rebuild Domain Tree", + "rebuildDesc": "Generate a completely new domain tree based on all document contents", + "keepOption": "Keep Unchanged", + "keepDesc": "Keep the current domain tree structure unchanged without any modifications" + } + }, + "domain": { + "title": "Domain Knowledge Tree", + "addRootTag": "Add Root Tag", + "addFirstTag": "Add First Tag", + "noTags": "No domain tags available", + "docStructure": "Document Structure", + "noToc": "No table of contents available. Please upload and process the document first.", + "editTag": "Edit Tag", + "deleteTag": "Delete Tag", + "addChildTag": "Add Child Tag", + "deleteTagConfirmTitle": "Delete Tag", + "deleteTagConfirmMessage": "Are you sure you want to delete tag \"{{tag}}\"?", + "deleteWarning": "This action will delete this tag and all its child tags, questions, and datasets. This cannot be undone!", + "dialog": { + "addTitle": "Add Tag", + "editTitle": "Edit Tag", + "addChildTitle": "Add Child Tag", + "inputRoot": "Please enter a new root tag name", + "inputEdit": "Please edit the tag name", + "inputChild": "Please add a child tag for \"{label}\"", + "labelName": "Tag Name", + "saving": "Saving...", + "save": "Save", + "deleteConfirm": "Are you sure to delete tag \"{label}\"?", + "deleteWarning": "This action will delete all child tags and cannot be undone.", + "emptyLabel": "Tag name cannot be empty" + }, + "tabs": { + "tree": "Domain Tree", + "structure": "Document Structure" + }, + "errors": { + "saveFailed": "Failed to save tags" + }, + "messages": { + "updateSuccess": "Tags updated successfully" + } + }, + "export": { + "alpacaSettings": "Alpaca Format Settings", + "questionFieldType": "Question Field Type", + "useInstruction": "Use instruction field", + "useInput": "Use input field", + "customInstruction": "Custom Instruction Content", + "instructionPlaceholder": "Enter fixed instruction content", + "instructionHelperText": "When using input field, you can specify fixed instruction content here", + "title": "Export", + "format": "Format", + "fileFormat": "File Format", + "systemPrompt": "System Prompt", + "systemPromptPlaceholder": "Please enter system prompt...", + "ReasoninglanguagePlaceholder": "Please enter Reasoning language : English or Chinese or others", + "onlyConfirmed": "Only export confirmed data", + "example": "Format Example", + "confirmExport": "Confirm Export", + "includeCOT": "Include Chain of Thought", + "cotDescription": "Includes the reasoning process before the final answer", + "customFormat": "Custom Format", + "customFormatSettings": "Custom Format Settings", + "questionFieldName": "Question Field Name", + "multilingualThinkingFormat": "Multilingual‑Thinking", + "Reasoninglanguage": "Reasoning language", + "sampleInstruction": "Human instruction (required)", + "sampleOutput": "Model response (required)", + "sampleSystem": "System prompt (optional)", + "sampleInstruction2": "Second instruction", + "sampleOutput2": "Second response", + "sampleSystemShort": "System prompt", + "fixedInstruction": "Fixed instruction content", + "sampleInput": "Human question (required)", + "sampleInput2": "Second question", + "sampleInputOptional": "Human input (optional)", + "sampleUserMessage": "Human instruction", + "sampleAssistantMessage": "Model response", + "sampleAnalysis": "Model's chain of thought content", + "sampleFinal": "Model response", + "sampleThinking": "Model's chain of thought content", + "fetchLabelStatsError": "Failed to fetch label statistics:" + }, + "import": { + "title": "Import", + "fileUpload": "File Upload", + "fileUploadDescription": "Upload local files to import datasets", + "mapFields": "Field Mapping", + "importing": "Importing", + "uploadFile": "Upload File", + "supportedFormats": "Supports JSON, JSONL, CSV format files", + "dragDropFile": "Drag and drop files here or click to select files", + "dropFileHere": "Release to upload file", + "maxFileSize": "Maximum file size: 50MB", + "processingFile": "Processing file...", + "uploadedFiles": "Uploaded Files", + "uploadError": "File upload failed, please check if the file format is correct", + "selectFromSource": "Select dataset from {{source}}", + "sourceDescription": "Enter dataset name or keywords to search", + "datasetName": "Dataset Name", + "hfPlaceholder": "e.g.: squad, glue, imdb", + "msPlaceholder": "e.g.: damo/nlp_bert_document-classification", + "search": "Search", + "searching": "Searching", + "searchResults": "Search Results", + "downloads": "Downloads", + "download": "Download", + "downloading": "Downloading", + "hfNote": "Note: Downloading large datasets may take a long time, it is recommended to select smaller datasets for testing.", + "msNote": "Note: ModelScope dataset download requires network connection, please ensure network connectivity.", + "fieldMapping": "Field Mapping", + "mappingDescription": "Please map the source data fields to target fields. The system has automatically identified possible mapping relationships, you can adjust as needed.", + "selectMapping": "Select Field Mapping", + "questionField": "Question Field", + "answerField": "Answer Field", + "cotField": "Chain of Thought Field", + "tagsField": "Tags Field", + "selectField": "Select Field", + "questionDesc": "User's question or input content (required)", + "answerDesc": "AI's answer or output content (required)", + "cotDesc": "Chain of thought or reasoning process (optional)", + "tagsDesc": "Tag array, multiple tags separated by commas (optional)", + "dataPreview": "Data Preview", + "previewNote": "Shows first 3 records, each field value displays up to 100 characters", + "confirmMapping": "Confirm Mapping", + "requiredFields": "Please select at least question and answer field mappings", + "mappingRequired": "Question and answer fields are required", + "duplicateMapping": "Cannot map multiple target fields to the same source field", + "noPreviewData": "No preview data available", + "preparingData": "Preparing data...", + "uploadingData": "Uploading data...", + "processing": "Processing... {{processed}}/{{total}}", + "completed": "Import completed", + "importStats": "Import Statistics", + "total": "Total: {{count}}", + "success": "Success: {{count}}", + "failed": "Failed: {{count}}", + "source": "Data Source", + "description": "Description", + "errors": "Error Messages", + "moreErrors": "{{count}} more errors not shown...", + "importSuccess": "Dataset import completed!", + "enterDatasetName": "Please enter dataset name", + "noDatasetFound": "No matching datasets found", + "complete": "Complete", + "addToEval": "Add to Eval Dataset", + "addToEvalSuccess": "Successfully added to eval dataset", + "addToEvalFailed": "Failed to add", + "generateEvalVariant": "Generate Eval Variant", + "selectModelFirst": "Please select a model first", + "generateVariantFailed": "Failed to generate variant", + "saveVariantSuccess": "Saved to eval dataset", + "saveVariantFailed": "Failed to save", + "evalVariantTitle": "Generate Evaluation Variant", + "evalVariantHint": "AI has generated a new test variant based on the original Q&A. You can edit and save it.", + "saveToEval": "Save to Eval Dataset", + "evalVariantConfigHint": "Please select the question type and quantity. AI will rewrite based on the current Q&A pair.", + "questionType": "Question Type", + "typeOpenEnded": "Open-ended", + "typeSingleChoice": "Single Choice", + "typeMultipleChoice": "Multiple Choice", + "typeTrueFalse": "True/False", + "typeShortAnswer": "Short Answer", + "generateCount": "Generate Count", + "evalVariantPreviewHint": "You can edit the generated questions and save them to the evaluation set after confirmation.", + "questionIndex": "Question {{index}}", + "options": "Options (JSON Array)", + "optionsHint": "e.g.: [\"Option A\", \"Option B\"]", + "answerArrayHint": "For multiple choice, please enter an array, e.g. [\"A\", \"C\"]", + "answerBoolHint": "For True/False, please enter ✅ or ❌", + "evalVariantPreviewTitle": "Confirm Generated Questions", + "generate": "Generate" + }, + "export_extended": { + "answerFieldName": "Answer Field Name", + "cotFieldName": "Cot Field Name", + "includeLabels": "Include Labels", + "includeChunk": "Include Text Chunk", + "questionOnly": "Export Questions Only", + "localTab": "Local Export", + "llamaFactoryTab": "Llama Factory", + "huggingFaceTab": "HuggingFace", + "configExists": "Configuration File Exists", + "configPath": "Configuration File Path", + "updateConfig": "Update LLaMA Factory Configuration", + "noConfig": "No configuration file exists, click the button below to generate", + "generateConfig": "Generate LLaMA Factory Configuration", + "huggingFaceComingSoon": "HuggingFace export feature coming soon", + "uploadToHuggingFace": "Upload to HuggingFace", + "datasetName": "Dataset Name", + "datasetNameHelp": "Format: username/dataset-name", + "privateDataset": "Private Dataset", + "datasetSettings": "Dataset Settings", + "exportOptions": "Export Options", + "uploadSuccess": "Dataset uploaded successfully to HuggingFace", + "viewOnHuggingFace": "View on HuggingFace", + "noTokenWarning": "Hugging Face Token not found. Please configure it in project settings.", + "goToSettings": "Go to Settings", + "tokenHelp": "You can get your token from HuggingFace settings page" + }, + "datasets": { + "loadingDataset": "Loading Dataset...", + "datasetNotFound": "Dataset Not Found", + "optimizeTitle": "AI Optimize", + "optimizeAdvice": "Optimize Advice", + "optimizePlaceholder": "Please enter your suggestions for improving the answer, and AI will optimize the answer and reasoning chain based on your suggestions", + "generatingDataset": "Generating Dataset", + "aiOptimizeAdvicePlaceholder": "Please enter your suggestions for improving the answer, and AI will optimize the answer and reasoning chain based on your suggestions", + "aiOptimizeAdvice": "Please enter your suggestions for improving the answer, and AI will optimize the answer and reasoning chain based on your suggestions", + "aiOptimize": "AI Optimize", + "partialSuccess": "Partially successful dataset generation ({{successCount}}/{{total}}), {{failCount}} questions failed", + "generating": "Generating Dataset", + "generateError": "Failed to generate dataset", + "management": "Datasets", + "question": "Question", + "filterAll": "All", + "filterConfirmed": "Confirmed", + "filterUnconfirmed": "Unconfirmed", + "createdAt": "Created At", + "model": "Model", + "domainTag": "Domain Tag", + "cot": "COT", + "answer": "Answer", + "chunkId": "Text Chunk", + "confirmed": "Confirmed", + "noTag": "No Tag", + "noData": "No Data", + "rowsPerPage": "Rows per page", + "pagination": "{{from}}-{{to}} of {{count}}", + "confirmDeleteMessage": "Are you sure you want to delete this dataset? This action cannot be undone.", + "questionLabel": "Question", + "fetchFailed": "Failed to fetch dataset", + "deleteFailed": "Failed to delete dataset", + "deleteSuccess": "Delete successful", + "exportSuccess": "Dataset exported successfully", + "exportFailed": "Export failed", + "exportProgress": "Export Progress", + "exportingData": "Exporting dataset", + "processedCount": "Processed {{processed}} / {{total}} items", + "exportInProgress": "Fetching data, please wait...", + "exportFinalizing": "Generating file, almost done...", + "loading": "Loading datasets...", + "stats": "Total {{total}} datasets, {{confirmed}} confirmed ({{percentage}}%)", + "selected": "Total selected: {{count}}", + "batchconfirmDeleteMessage": "Are you sure you want to delete {{count}} selected questions? This action cannot be undone.", + "batchDelete": "Batch Delete", + "batchDeleteProgress": "Completed: {{completed}}/{{total}}", + "batchDeleteCount": "Delete count: {{count}}", + "searchPlaceholder": "Search datasets...", + "fieldQuestion": "Question", + "fieldAnswer": "Answer", + "fieldCOT": "COT", + "fieldLabel": "Domain Label", + "moreFilters": "More Filters", + "filtersTitle": "Filter Options", + "filterConfirmationStatus": "Confirmation Status", + "filterCotStatus": "Chain of Thought Status", + "filterHasCot": "Has CoT", + "filterNoCot": "No CoT", + "filterScoreRange": "Rating Range", + "filterNoteKeyword": "Note Keyword", + "filterNoteKeywordPlaceholder": "Enter note keyword...", + "filterChunkName": "Chunk Name", + "filterChunkNamePlaceholder": "Enter chunk name...", + "filterCustomTag": "Custom Tag", + "resetFilters": "Reset", + "applyFilters": "Apply", + "viewDetails": "View Details", + "datasetDetail": "Dataset Details", + "metadata": "Metadata", + "confirmSave": "Confirm Save", + "unconfirm": "Unconfirm", + "unconfirming": "Unconfirming...", + "uncategorized": "Uncategorized", + "questionCount": "{{count}} Questions", + "source": "Source", + "generateDataset": "Generate Dataset", + "generateNotImplemented": "Dataset generation is not implemented", + "generateSuccess": "Successfully generated dataset for: {{question}}", + "generateFailed": "Failed to generate dataset: {{error}}", + "noTagsAndQuestions": "No tags and questions available", + "answerCount": "{{count}} Answers", + "answered": "Answered", + "enableShortcuts": "Page shortcut key", + "shortcutsHelp": "Press ← forward, press → backward, press y to confirm, press d to delete", + "filterDistill": "Distilled Dataset", + "filterDistillYes": "Distilled Dataset", + "filterDistillNo": "Non-distilled Dataset", + "evaluation": "Dataset Evaluation", + "rating": "Rating", + "ratingExcellent": "Excellent", + "ratingGood": "Good", + "ratingAverage": "Average", + "ratingPoor": "Poor", + "ratingVeryPoor": "Very Poor", + "ratingUnrated": "Unrated", + "customTags": "Custom Tags", + "addCustomTag": "Add custom tag...", + "note": "Note", + "addNote": "Add note...", + "noNote": "No notes", + "clickToAddNote": "Click to add note...", + "enterNote": "Enter note...", + "noteShortcuts": "Ctrl+Enter to save, Esc to cancel", + "aiEvaluation": "AI Quality Assessment", + "addToEval": "Add to Eval Dataset", + "addToEvalSuccess": "Successfully added to eval dataset", + "addToEvalFailed": "Failed to add", + "generateEvalVariant": "Generate Eval Variant", + "generateVariantFailed": "Failed to generate variant", + "saveVariantSuccess": "Saved to eval dataset", + "saveVariantFailed": "Failed to save", + "evalVariantTitle": "Generate Evaluation Variant", + "evalVariantPreviewTitle": "Confirm Generated Questions", + "saveToEval": "Save to Eval Dataset", + "evalVariantConfigHint": "Please select the question type and quantity. AI will rewrite based on the current Q&A pair.", + "questionType": "Question Type", + "typeOpenEnded": "Open-ended", + "typeSingleChoice": "Single Choice", + "typeMultipleChoice": "Multiple Choice", + "typeTrueFalse": "True/False", + "typeShortAnswer": "Short Answer", + "generateCount": "Generate Count", + "evalVariantPreviewHint": "You can edit the generated questions and save them to the evaluation set after confirmation.", + "questionIndex": "Question {{index}}", + "options": "Options (JSON Array)", + "optionsHint": "e.g.: [\"Option A\", \"Option B\"]", + "answerArrayHint": "For multiple choice, please enter an array, e.g. [\"A\", \"C\"]", + "answerBoolHint": "For True/False, please enter ✅ or ❌", + "generate": "Generate", + "updateSuccess": "Update successful", + "updateFailed": "Update failed", + "evaluate": "Evaluate", + "evaluating": "Evaluating...", + "batchEvaluate": "Batch Evaluate", + "selectModelFirst": "Please select a model first", + "evaluateSuccess": "Evaluation completed! Score: {{score}}/5", + "evaluateFailed": "Evaluation failed", + "evaluateError": "Evaluation failed: {{error}}", + "batchEvaluateStarted": "Batch evaluation task started, processing in background", + "batchEvaluateStartFailed": "Failed to start batch evaluation", + "batchEvaluateFailed": "Batch evaluation failed: {{error}}", + "scoreRange": "{{min}} - {{max}} points", + "singleTurn": "Single-turn Q&A Dataset", + "multiTurn": "Multi-turn Conversation Dataset", + "imageQA": "Image Q&A Dataset", + "conversationDetail": "Multi-turn Conversation Details", + "conversationContent": "Conversation Content", + "basicInfo": "Basic Information", + "firstQuestion": "First Question", + "conversationScenario": "Conversation Scenario", + "conversationRounds": "Conversation Rounds", + "modelUsed": "Model Used", + "qualityScore": "Quality Score", + "notes": "Notes", + "createTime": "Create Time", + "notSet": "Not Set", + "noTags": "No Tags", + "noNotes": "No Notes", + "notEvaluated": "Not Evaluated", + "round": "Round {{round}}", + "system": "System", + "user": "User", + "assistant": "Assistant", + "confirmDelete": "Confirm Delete", + "confirmDeleteConversation": "Are you sure you want to delete this multi-turn conversation dataset? This action cannot be undone.", + "conversationNotFound": "Conversation dataset not found", + "fetchDataFailed": "Failed to fetch data", + "saveFailed": "Save failed", + "saveSuccess": "Save successful", + "saving": "Saving...", + "inputTagsPlaceholder": "Enter tags, separated by spaces", + "addNotesPlaceholder": "Add notes", + "noConversations": "No multi-turn conversations", + "notRated": "Not Rated", + "minScore": "Min Score", + "maxScore": "Max Score", + "unconfirmed": "Unconfirmed" + }, + "rating": { + "veryPoor": "Very Poor", + "poor": "Poor", + "belowAverage": "Below Average", + "fair": "Fair", + "average": "Average", + "good": "Good", + "veryGood": "Very Good", + "excellent": "Excellent", + "outstanding": "Outstanding", + "perfect": "Perfect", + "unrated": "Unrated" + }, + "tags": { + "noTags": "No tags", + "addTag": "Add tag...", + "addCustomTag": "Add custom tag", + "maxTagsReached": "Maximum {{maxTags}} tags reached", + "availableTagsHint": "Select from existing tags or enter new ones" + }, + "update": { + "newVersion": "New Version", + "newVersionAvailable": "New Version Available", + "currentVersion": "Current Version", + "latestVersion": "Latest Version", + "downloadNow": "Download Now", + "downloading": "Downloading", + "installNow": "Install Now", + "updating": "Updating...", + "updateNow": "Update Now", + "viewRelease": "View Release Notes", + "checking": "Checking for updates...", + "noUpdates": "Already up to date", + "updateError": "Update Error", + "updateSuccess": "Update Successful", + "restartRequired": "Restart Required", + "restartNow": "Restart Now", + "restartLater": "Restart Later" + }, + "datasetSquare": { + "title": "Dataset Square", + "subtitle": "Discover and explore various public dataset resources to support your model training and research", + "searchPlaceholder": "Search dataset keywords...", + "searchVia": "Search via", + "categoryTitle": "Dataset Categories", + "categories": { + "all": "All", + "popular": "Popular", + "chinese": "Chinese Resources", + "english": "English Resources", + "research": "Research Data", + "multimodal": "Multimodal" + }, + "foundResources": "Found {{count}} dataset resources", + "currentFilter": "Current filter: {{category}}", + "noDatasets": "No datasets found matching your criteria", + "tryOtherCategories": "Please try other categories or return to view all datasets", + "dataset": "Dataset", + "viewDataset": "View Dataset" + }, + "playground": { + "title": "Model Testing", + "selectModelFirst": "Please select a model", + "sendFirstMessage": "Send your first message to start testing", + "inputMessage": "Enter message...", + "send": "Send", + "outputMode": "Output Mode", + "normalOutput": "Normal Output", + "streamingOutput": "Streaming Output", + "clearConversation": "Clear Conversation", + "selectModelMax3": "Please select up to 3 models to test", + "reasoningProcess": "Reasoning Chain" + }, + "chunks": { + "title": "Text Chunk", + "defaultTitle": "Default Title" + }, + "documentation": "Documentation", + "models": { + "configNotFound": "Model config not found", + "parseError": "Failed to parse model config", + "fetchFailed": "Failed to fetch model", + "saveFailed": "Failed to save model config", + "pleaseSelectModel": "Please select at least one model", + "title": "Model Settings", + "add": "Add Model", + "unselectedModel": "Unselected Model", + "unconfiguredAPIKey": "Unconfigured API Key", + "saveAllModels": "Save All Models", + "edit": "Edit", + "delete": "Delete", + "modelId": "Model ID", + "modelName": "Model Name", + "modelNamePlaceholder": "Enter model name (optional, defaults to Model ID)", + "modelIdPlaceholder": "Enter model ID (e.g., gpt-4o)", + "endpoint": "Endpoint", + "apiKey": "API Key", + "provider": "Provider", + "localModel": "Local Model", + "apiKeyConfigured": "API Key Configured", + "apiKeyNotConfigured": "API Key Not Configured", + "temperature": "Temperature", + "maxTokens": "Max Tokens", + "maxTokensInputTip": "Slider range: 1-{{max}}. You can also input any positive integer.", + "topP": "Top P", + "type": "Model Type", + "text": "Large Language Model", + "vision": "Vision Large Model", + "typeTips": "If you want to use a custom vision model to parse PDFs, please configure at least one vision large model", + "refresh": "Refresh Models", + "configuredModels": "Configured Models", + "unconfiguredModels": "Unconfigured Models", + "noConfiguredModels": "No configured models", + "noUnconfiguredModels": "No unconfigured models", + "checkEndpointHealth": "Check endpoint health", + "checkAllEndpointHealth": "Check all endpoints", + "endpointHealthy": "Endpoint is healthy", + "endpointCheckFailed": "Endpoint check failed", + "endpointMissing": "Endpoint is empty", + "endpointReachableModelMissing": "Endpoint reachable, but current model is not in the returned model list", + "healthCheckSummary": "Health check completed: {{okCount}} healthy, {{failCount}} failed", + "checking": "Checking...", + "healthy": "Healthy", + "reachable": "Reachable", + "unhealthy": "Unhealthy", + "notChecked": "Not checked" + }, + "stats": { + "ongoingProjects": "Ongoing Projects", + "questionCount": "Question Count", + "generatedDatasets": "Generated Datasets", + "supportedModels": "Supported Models" + }, + "migration": { + "title": "Project Migration", + "description": "Some projects need to be migrated to the database. Migration can improve performance and support more features.", + "projectsList": "Unmigrated Projects", + "migrate": "Start Migration", + "migrating": "Migrating...", + "success": "Successfully migrated {{count}} projects", + "failed": "Migration failed", + "checkFailed": "Failed to check unmigrated projects", + "checkError": "Error checking unmigrated projects", + "starting": "Starting migration task...", + "processing": "Processing migration task...", + "completed": "Migration completed", + "startFailed": "Failed to start migration task", + "statusFailed": "Failed to get migration status", + "taskNotFound": "Migration task not found", + "progressStatus": "Migrated {{completed}}/{{total}} projects", + "openDirectory": "Open Directory", + "deleteDirectory": "Delete Directory", + "confirmDelete": "Are you sure you want to delete this project directory? This action cannot be undone.", + "openDirectoryFailed": "Failed to open project directory", + "deleteDirectoryFailed": "Failed to delete project directory" + }, + "distill": { + "title": "Distill", + "generateRootTags": "Generate Root Tags", + "generateSubTags": "Generate Sub Tags", + "generateQuestions": "Generate Questions", + "generateRootTagsTitle": "Generate Root Domain Tags", + "generateSubTagsTitle": "Generate Sub Tags for {{parentTag}}", + "generateQuestionsTitle": "Generate Questions for {{tag}}", + "parentTag": "Parent Tag", + "parentTagPlaceholder": "Enter parent tag name (e.g., Sports, Technology)", + "parentTagHelp": "Enter a domain topic, and the system will generate related tags based on it", + "generateQuestionsError": "Failed to generate questions", + "tagCount": "Number of Tags", + "tagCountHelp": "Enter the number of tags to generate, maximum is 100", + "questionCount": "Number of Questions", + "questionCountHelp": "Enter the number of questions to generate, maximum is 100", + "generatedTags": "Generated Tags", + "generatedQuestions": "Generated Questions", + "tagPath": "Tag Path", + "noTags": "No Tags", + "noQuestions": "No Questions", + "clickGenerateButton": "Click the generate button above to create tags", + "selectModelFirst": "Please select a model first", + "selectModel": "Select Model", + "generateTagsError": "Failed to generate tags", + "generateTags": "Generate Tags", + "subTags": "sub-tags", + "questions": "questions", + "deleteTagConfirmTitle": "Confirm to delete tag?", + "editTagTitle": "Edit Tag", + "tagName": "Tag Name", + "labelRequired": "Tag name cannot be empty", + "tagUpdateSuccess": "Tag updated successfully", + "tagUpdateFailed": "Failed to update tag", + "unknownTag": "Unknown Tag", + "autoDistillButton": "Auto Distill Dataset", + "autoDistillTitle": "Automated Dataset Distillation Configuration", + "distillTopic": "Distillation Topic", + "tagLevels": "Tag Levels", + "tagLevelsHelper": "Set the number of levels, maximum is {{max}}", + "tagsPerLevel": "Tags Per Level", + "tagsPerLevelHelper": "Number of sub-tags to generate under each parent tag, maximum is {{max}}", + "questionsPerTag": "Questions Per Tag", + "questionsPerTagHelper": "Number of questions to generate for each leaf tag, maximum is {{max}}", + "estimationInfo": "Task Estimation Info", + "estimatedTags": "Estimated Tags", + "estimatedQuestions": "Estimated Questions", + "currentTags": "Current Tags", + "currentQuestions": "Current Questions", + "newTags": "New Tags", + "newQuestions": "New Questions", + "startAutoDistill": "Start Auto Distillation", + "autoDistillProgress": "Auto Distillation Progress", + "overallProgress": "Overall Progress", + "tagsProgress": "Tag Building Progress", + "questionsProgress": "Question Generation Progress", + "currentStage": "Current Stage", + "realTimeLogs": "Real-time Logs", + "waitingForLogs": "Waiting for logs...", + "autoDistillStarted": "{{time}} Auto distillation task started", + "autoDistillInsufficientError": "Current configuration will not produce new tags or questions, please adjust parameters", + "stageInitializing": "Initializing...", + "stageBuildingLevel1": "Building Level 1 Tags", + "stageBuildingLevel2": "Building Level 2 Tags", + "stageBuildingLevel3": "Building Level 3 Tags", + "stageBuildingLevel4": "Building Level 4 Tags", + "stageBuildingLevel5": "Building Level 5 Tags", + "stageBuildingQuestions": "Generating Questions", + "stageBuildingDatasets": "Building Datasets", + "stageCompleted": "Task Completed", + "datasetsProgress": "Datasets Progress", + "rootTopicHelperText": "By default, the project name is used as the top-level distillation theme. If you need to change it, please go to the project settings to modify the project name.", + "addChildTag": "Add Child Tag", + "datasetType": "Dataset Type", + "singleTurnDataset": "Single-turn Dataset", + "multiTurnDataset": "Multi-turn Dataset", + "bothDatasetTypes": "Generate Both Dataset Types", + "autoDistillTaskDetail": "Auto Distill Task: {{topic}}", + "backgroundTaskCreated": "Background distill task created. You can check the progress in the task management center.", + "backgroundTaskFailed": "Failed to create background task", + "taskExecutionError": "Task execution error: {{error}}" + }, + "tasks": { + "pending": "{{count}} tasks are processing", + "completed": "tasks are completed", + "title": "Task Management Center", + "loading": "Loading tasks...", + "empty": "No tasks found", + "confirmDelete": "Are you sure you want to delete this task?", + "confirmAbort": "Are you sure you want to abort this task? The task will be stopped.", + "deleteSuccess": "Task deleted", + "deleteFailed": "Failed to delete task", + "abortSuccess": "Task aborted", + "abortFailed": "Failed to abort task", + "status": { + "processing": "Processing", + "completed": "Completed", + "failed": "Failed", + "aborted": "Aborted", + "unknown": "Unknown" + }, + "types": { + "text-processing": "Text Processing", + "file-processing": "File Processing", + "data-cleaning": "Data Cleaning", + "question-generation": "Question Generation", + "answer-generation": "Answer Generation", + "eval-generation": "Evaluation Generation", + "multi-turn-generation": "Multi-turn Generation", + "image-question-generation": "Image Question Generation", + "data-distillation": "Data Distillation", + "pdf-processing": "PDF Processing" + }, + "filters": { + "status": "Task Status", + "type": "Task Type" + }, + "actions": { + "refresh": "Refresh task list", + "delete": "Delete task", + "abort": "Abort task" + }, + "table": { + "type": "Type", + "status": "Status", + "progress": "Progress", + "note": "Note", + "createTime": "Created", + "endTime": "Completed", + "duration": "Duration", + "model": "Model", + "detail": "Details", + "actions": "Actions" + }, + "duration": { + "seconds": "{{seconds}}s", + "minutes": "{{minutes}}m {{seconds}}s", + "hours": "{{hours}}h {{minutes}}m" + }, + "fetchFailed": "Failed to fetch task list", + "createSuccess": "Task created successfully", + "createFailed": "Failed to create task", + "multiTurnCreateSuccess": "Multi-turn conversation dataset task created successfully", + "notes": { + "selectedChunks": "{{count}} chunks selected", + "fileBatch": "File processing params: {{count}} files (strategy: {{strategy}})", + "jsonParams": "Task parameters configured", + "noChunksQuestion": "No chunks require question generation", + "noChunksCleaning": "No chunks require cleaning", + "processingFailed": "Processing failed: {{error}}", + "questionSummary": "Processed {{processed}}/{{total}}, succeeded {{succeeded}}, failed {{failed}}, questions generated {{generated}}", + "datasetSummary": "Processed {{processed}}/{{total}}, succeeded {{succeeded}}, failed {{failed}}, datasets generated {{generated}}", + "cleaningSummary": "Processed {{processed}}/{{total}}, succeeded {{succeeded}}, failed {{failed}}, original length {{original}}, cleaned length {{cleaned}}", + "genericSummary": "Processed {{processed}}/{{total}}, succeeded {{succeeded}}, failed {{failed}}" + } + }, + "gaPairs": { + "title": "Genre-Audience Pairs Management", + "loading": "Loading GA pairs...", + "addPair": "Add GA Pair", + "saveChanges": "Save Changes", + "saving": "Saving...", + "restoreBackup": "Restore Backup", + "noGaPairsTitle": "No Genre-Audience Pairs Found", + "noGaPairsDescription": "Generate AI-powered Genre-Audience pairs for this file", + "generateGaPairs": "Generate Genre-Audience Pairs", + "generating": "Generating...", + "generateMore": "Generate More Genre-Audience Pairs", + "activePairs": "Active Genre-Audience Pairs ({{active}}/{{total}})", + "pairNumber": "Genre-Audience Pair #{{number}}", + "active": "Active", + "deleteTooltip": "Delete GA Pair", + "genre": "Genre", + "genreDescription": "Genre Description", + "audience": "Audience", + "audienceDescription": "Audience Description", + "addDialogTitle": "Add New Genre-Audience Pair", + "genreTitle": "Genre Title", + "audienceTitle": "Audience Title", + "genreTitlePlaceholder": "Enter the genre title...", + "genreDescPlaceholder": "Describe the genre in detail...", + "audienceTitlePlaceholder": "Enter the audience title...", + "audienceDescPlaceholder": "Describe the target audience in detail...", + "cancel": "Cancel", + "addPairButton": "Add Genre-Audience Pair", + "requiredFields": "Genre Title and Audience Title are required", + "restoredFromBackup": "Restored from backup", + "allPairsDeleted": "All GA pairs deleted successfully", + "pairsSaved": "{{count}} GA pairs saved successfully", + "additionalPairsGenerated": "Successfully generated {{count}} additional Genre-Audience pairs. Total: {{total}}", + "validationError": "GA pair {{number}}: Genre and Audience titles are required", + "loadError": "Unable to load GA pairs: {{error}}", + "generateError": "Failed to generate GA pairs", + "saveError": "Failed to save GA pairs", + "noActiveModel": "Please configure an AI model in settings before generating GA pairs.", + "contentTooShort": "The file content is too short or not suitable for GA pair generation.", + "configError": "AI model configuration error. The required dependencies may not be installed.", + "serverError": "Server error ({{status}}). Please try again later.", + "emptyResponse": "Empty response from generation service", + "generationFailed": "Generation failed", + "saveOperationFailed": "Save operation failed", + "serviceNotAvailable": "GA Pairs generation service is not available. Please check your API configuration.", + "requestFailed": "Request failed ({{status}}). Please try again.", + "internalServerError": "Internal server error occurred.", + "batchGenerate": "Batch Generate GA Pairs", + "batchGenerateDescription": "Will batch generate GA pairs for {{count}} selected files. This operation may take some time.", + "appendMode": "Append Mode", + "appendModeDescription": "Generate additional GA pairs for files that already have GA pairs, rather than overwriting", + "selectAtLeastOneFile": "Please select at least one file first", + "noDefaultModel": "No default model set, please configure a model in project settings first", + "incompleteModelConfig": "Model configuration is incomplete, please check model settings", + "missingApiKey": "Model API key not configured, please add API key in model settings", + "loadingProjectModel": "Loading project model...", + "usingModel": "Using model", + "startGeneration": "Start Generation", + "batchGenCompleted": "Batch generation completed! Successfully generated GA pairs for {{success}}/{{total}} files.", + "generationError": "Error occurred during generation: {{error}}", + "fetchProjectInfoFailed": "Failed to fetch project info: {{status}}", + "fetchModelConfigFailed": "Failed to fetch model config: {{status}}", + "fetchProjectModelError": "Error fetching project model configuration", + "batchGenerationFailed": "Batch GA pair generation failed", + "batchGenerationSuccess": "Successfully generated GA pairs for {{count}} files", + "selectAllFiles": "Select All", + "deselectAllFiles": "Deselect All", + "batchGenerateTitle": "Batch Generate GA Pairs", + "generationMode": "Generation Mode", + "aiGenerateMode": "AI Generate", + "manualAddMode": "Manual Add", + "genreDesc": "Genre Description", + "audienceDesc": "Audience Description", + "manualGaPairRequired": "Please fill in Genre Title and Audience Title", + "batchAddManual": "Batch Add" + }, + "batchEdit": { + "title": "Batch Edit Text Chunks", + "batchEdit": "Batch Edit", + "batchEditTooltip": "Batch edit selected text chunks", + "position": "Add Position", + "atBeginning": "Add at Beginning", + "atEnd": "Add at End", + "contentToAdd": "Content to Add", + "contentPlaceholder": "Enter content to add to text chunks...", + "contentRequired": "Please enter content to add", + "contentHelp": "This content will be added to all selected text chunks", + "preview": "Preview", + "allChunksSelected": "All {{count}} text chunks selected", + "selectedChunks": "{{selected}} / {{total}} text chunks selected", + "processing": "Processing...", + "applyToChunks": "Apply to {{count}} chunks", + "editSuccess": "Successfully edited {{count}} text chunks", + "editFailed": "Batch edit failed", + "previewNote": "The above is a preview of the first selected text chunk. All selected text chunks will undergo the same modification" + }, + "errors": { + "projectIdRequired": "Project ID cannot be empty", + "getDatasetsFailed": "Failed to get datasets", + "getTagStatsFailed": "Failed to get tag statistics", + "deleteFileFailed": "Error deleting file", + "recordNotFound": "Current record does not exist", + "mineruTokenNotFound": "Token configuration not found, please check if MinerU token is configured in task settings", + "mineruLocalUrlNotFound": "MinerU local URL configuration not found, please check if MinerU local URL is configured in task settings" + }, + "sampleData": { + "questionContent": "Question content", + "answerContent": "Answer content", + "cotContent": "Chain of thought content", + "domainLabel": "Domain label", + "textChunk": "Text chunk" + }, + "exportDialog": { + "balancedExport": "Balanced Export", + "balancedExportTitle": "Balanced Export Settings", + "balancedExportDescription": "Configure the data volume for each category based on domain tags to achieve balanced dataset export", + "quickSettings": "Quick Settings", + "setAllTo50": "Set all to 50", + "setAllTo100": "Set all to 100", + "setAllTo200": "Set all to 200", + "customAmount": "Custom amount", + "tagName": "Tag Name", + "availableCount": "Available Count", + "exportCount": "Export Count", + "settings": "Settings", + "totalExportCount": "Total export count", + "tagCount": "Tag count", + "export": "Export" + }, + "imageDatasets": { + "title": "Image Q&A Dataset", + "subtitle": "Manage and optimize your image Q&A datasets", + "description": "Manage and optimize your image Q&A datasets.", + "searchPlaceholder": "Search questions or answers...", + "noAnswer": "No answer", + "labels": "Labels", + "typeLabel": "Label", + "typeCustom": "Custom", + "typeText": "Text", + "unscored": "Unscored", + "confirmed": "Confirmed", + "unconfirmed": "Unconfirmed", + "view": "View Details", + "evaluate": "Quality Assessment", + "delete": "Delete", + "deleteConfirm": "Are you sure you want to delete this dataset?", + "imageName": "Image Name", + "status": "Status", + "scoreRange": "Score Range", + "noData": "No image datasets", + "noDataTip": "Please generate Q&A datasets in Image Management first", + "fetchFailed": "Failed to fetch datasets", + "fetchDetailFailed": "Failed to fetch detail", + "deleteSuccess": "Deleted successfully", + "deleteFailed": "Failed to delete", + "updateSuccess": "Updated successfully", + "updateFailed": "Failed to update", + "regenerateSuccess": "AI recognition successful", + "regenerateFailed": "AI recognition failed", + "notFound": "Dataset not found", + "detail": "Detail", + "image": "Image", + "question": "Question", + "answer": "Answer", + "selectLabels": "Select Labels", + "noLabels": "No labels selected", + "jsonPlaceholder": "Enter JSON format data...", + "metadata": "Metadata", + "score": "Score", + "tags": "Tags", + "addTag": "Add tag...", + "note": "Note", + "notePlaceholder": "Add note...", + "modelInfo": "Model Info", + "createdAt": "Created At", + "updatedAt": "Updated At", + "exportTitle": "Export Image Dataset", + "exportFormat": "Export Format", + "rawFormat": "Raw Format", + "customFormat": "Custom Format", + "exportImagesOption": "Export Image Files", + "exportImagesDesc": "Package all images into a ZIP file for download", + "includeImagePath": "Include Image Path in Dataset", + "includeImagePathDesc": "Add image path in question or answer (format: /images/image_name)", + "systemPrompt": "System Prompt (Optional)", + "systemPromptPlaceholder": "Enter system prompt...", + "confirmedOnly": "Export Confirmed Only", + "exportTip": "Label format answers will be automatically parsed to text (comma separated)", + "exportSuccess": "Dataset exported successfully", + "exportFailed": "Export failed", + "noDataToExport": "No data to export", + "exportImagesSuccess": "Image ZIP package exported successfully", + "exportImagesFailed": "Failed to export images" + }, + "images": { + "resolution": "Resolution", + "uploadTime": "Upload Time", + "fileName": "File Name", + "title": "Image Management", + "importImages": "Import Images", + "searchPlaceholder": "Search image name...", + "hasQuestions": "Question Status", + "hasDatasets": "Dataset Status", + "withQuestions": "With Questions", + "withoutQuestions": "Without Questions", + "withDatasets": "With Datasets", + "withoutDatasets": "Without Datasets", + "noImages": "No images", + "noImagesDescription": "Start importing images to create your first dataset", + "preview": "Preview", + "questions": "Questions", + "datasets": "Datasets", + "datasetCount": "Dataset Count", + "generateQuestions": "Generate Questions", + "generateDataset": "Generate Dataset", + "deleteConfirm": "Are you sure you want to delete this image?", + "deleteSuccess": "Deleted successfully", + "deleteFailed": "Delete failed", + "batchDelete": "Batch Delete", + "selectImagesToDelete": "Please select images to delete", + "batchDeleteConfirm": "Are you sure you want to delete {{count}} selected images?", + "batchDeleteSuccess": "Successfully deleted {{count}} images", + "batchDeletePartialSuccess": "Successfully deleted {{success}}, failed {{fail}}", + "batchDeleteFailed": "Batch delete failed", + "importTip": "Select one or more directories containing images. All images will be imported into the project (duplicate names will be overwritten)", + "selectDirectory": "Select Directory", + "directoryPath": "Directory Path", + "enterDirectoryPath": "e.g., /Users/username/Pictures", + "selectedDirectories": "Selected Directories", + "selectAtLeastOne": "Please select at least one directory", + "importSuccess": "Successfully imported {{count}} images", + "importFailed": "Import failed", + "startImport": "Start Import", + "addDirectory": "Add Directory", + "importFromDirectory": "Import from Directory", + "importFromPdf": "Import from PDF", + "importFromZip": "Import from ZIP", + "pdfImportTip": "Select a PDF file, the system will automatically convert it to images and import", + "zipImportTip": "Select a ZIP archive file, the system will automatically extract and import images from it", + "clickToSelectPdf": "Click to select PDF file", + "clickToSelectZip": "Click to select ZIP file", + "supportedFormat": "Supported format: PDF", + "supportedZipFormat": "Supported format: ZIP", + "fileSize": "File size", + "selectedFile": "Selected file", + "invalidPdfFile": "Please select a valid PDF file", + "invalidZipFile": "Please select a valid ZIP file", + "selectPdfFile": "Please select a PDF file", + "selectZipFile": "Please select a ZIP file", + "pdfImportSuccess": "Successfully imported {{count}} images from PDF \"{{name}}\"", + "pdfImportFailed": "PDF import failed", + "zipImportSuccess": "Successfully imported {{count}} images from ZIP \"{{name}}\"", + "zipImportFailed": "ZIP import failed", + "convertAndImport": "Convert and Import", + "extractAndImport": "Extract and Import", + "electronRequired": "This feature requires the desktop application", + "selectDirectoryFailed": "Failed to select directory", + "imageName": "Image Name", + "questionCount": "Question Count", + "questionCountHelp": "Generate 1-10 questions", + "size": "Size", + "dimensions": "Dimensions", + "currentModel": "Current Model", + "selectModelFirst": "Please select a model first", + "visionModelRequired": "Please select a vision-capable model (e.g., GPT-4 Vision, Claude, etc.)", + "countRange": "Question count should be between 1-10", + "questionsGenerated": "Successfully generated {{count}} questions", + "generateFailed": "Generation failed", + "question": "Question", + "questionPlaceholder": "Enter your question...", + "questionRequired": "Please enter a question", + "datasetGenerated": "Dataset generated successfully", + "autoGenerateQuestions": "Auto Generate Questions", + "autoGenerateConfirm": "The system will automatically generate questions for all images without questions. This will create a background task, and you can view the progress in Task Management.", + "taskCreated": "Task created successfully, processing in background", + "taskCreateFailed": "Failed to create task", + "manualAnnotation": "Manual Annotation", + "annotationTitle": "Image Annotation", + "imageInfo": "Image Info", + "annotatedCount": "Annotated", + "selectQuestion": "Select or Create Question", + "selectQuestionPlaceholder": "Select question template...", + "universalQuestions": "Universal Questions", + "independentQuestions": "Independent Questions", + "answerTypeText": "Text", + "answerTypeLabel": "Label", + "answerTypeCustomFormat": "Custom Format", + "usedTimes": "Used {{count}} times", + "answer": "Answer", + "answerPlaceholder": "Enter answer...", + "selectLabels": "Select Labels", + "availableLabels": "Available Labels", + "noLabelsAvailable": "No labels available", + "addNewLabel": "Add new label...", + "selectedLabels": "Selected", + "customFormatAnswer": "Custom Format Answer", + "formatRequirement": "Format Requirement", + "customFormatPlaceholder": "Enter JSON in the required format...", + "note": "Note", + "notePlaceholder": "Note (optional)", + "saveAndContinue": "Save & Continue", + "noImageSelected": "No image selected", + "noTemplateSelected": "Please select a question", + "answerRequired": "Please enter an answer", + "invalidJsonFormat": "Invalid JSON format", + "annotationSuccess": "Annotation saved successfully", + "annotationFailed": "Failed to save annotation", + "allQuestionsAnnotated": "All questions for this image have been annotated", + "allImagesAnnotated": "All questions for all images have been annotated", + "noQuestionsAssociated": "No questions associated with this image", + "loadImageDetailFailed": "Failed to load image details", + "answeredQuestions": "Annotated Questions", + "useTemplate": "Use Template", + "formatJson": "Format", + "jsonFormatHelp": "Please enter valid JSON format data", + "imageLoadError": "Failed to load image", + "annotate": "Annotate", + "annotateImage": "Annotate Image", + "createQuestion": "Create Question", + "createTemplate": "Create Question Template", + "aiGenerate": "AI Recognize", + "aiGenerateSuccess": "AI generation successful", + "aiGenerateFailed": "AI generation failed", + "missingParameters": "Missing required parameters", + "selectNewQuestion": "Select New Question", + "fetchTemplatesFailed": "Failed to fetch question templates", + "createTemplateSuccess": "Question template created successfully", + "createTemplateFailed": "Failed to create question template", + "updateTemplateSuccess": "Question template updated successfully", + "updateTemplateFailed": "Failed to update question template", + "deleteTemplateSuccess": "Question template deleted successfully", + "deleteTemplateFailed": "Failed to delete question template", + "template": { + "management": "Manage Question Templates", + "create": "Create Template", + "edit": "Edit Template", + "question": "Question Content", + "description": "Description", + "noTemplates": "No question templates yet, click create button to add", + "deleteConfirm": "Are you sure you want to delete this question template?", + "used": "Used", + "addLabel": "Add Label", + "customFormat": "Custom Format", + "customFormatHelp": "Enter JSON format output constraint", + "customFormatInfo": "This format will be provided to the LLM as a prompt to constrain output format", + "type": { + "label": "Question Type", + "universal": "Universal Question", + "independent": "Independent Question" + }, + "answerType": { + "label": "Answer Type", + "text": "Text", + "tags": "Tags", + "customFormat": "Custom Format" + }, + "errors": { + "questionRequired": "Please enter question content", + "labelsRequired": "Label type questions require at least one label", + "customFormatRequired": "Please enter custom format", + "invalidJson": "Invalid JSON format" + } + } + }, + "monitoring": { + "title": "Resource Monitoring Dashboard", + "timeRange": { + "24h": "Last 24 hours", + "7d": "Last 7 days", + "30d": "Last 30 days" + }, + "filters": { + "allProjects": "All Projects", + "allProviders": "All Providers", + "allStatus": "All Status" + }, + "status": { + "success": "Success", + "failed": "Failed" + }, + "actions": { + "export": "Export Report" + }, + "stats": { + "totalTokens": "Total Token Usage", + "avgTokensPerCall": "Avg Tokens per Call", + "totalCalls": "Total Calls", + "avgLatency": "Avg Latency", + "inputOutput": "Input: {{input}} · Output: {{output}}", + "successCalls": "{{count}} Success", + "failedCalls": "{{count}} Failed", + "failureRate": "{{rate}}% Failure Rate", + "basedOnSuccessCalls": "Based on {{count}} successful calls", + "noSuccessCalls": "No successful calls" + }, + "charts": { + "tokenTrend": "Token Usage Trend", + "inputLegend": "Input", + "outputLegend": "Output", + "distributionTitle": "Token Usage Distribution (by Model)", + "distributionSubtitle": "Resource usage share by model", + "tokensTooltip": "{{value}}K Tokens" + }, + "table": { + "title": "Usage Details", + "searchPlaceholder": "Search project, model, or failure reason...", + "empty": "No data", + "rowsPerPage": "Rows per page:", + "columns": { + "projectName": "Project", + "provider": "Provider", + "model": "Model", + "status": "Status", + "failureReason": "Failure Reason", + "inputTokens": "Input Tokens", + "outputTokens": "Output Tokens", + "totalTokens": "Total", + "calls": "Calls", + "avgLatency": "Avg Latency" + } + }, + "errors": { + "fetchSummaryFailed": "Failed to fetch monitoring summary", + "fetchLogsFailed": "Failed to fetch monitoring logs" + } + }, + "eval": { + "title": "Eval", + "datasets": "Eval Datasets", + "tasks": "Eval Tasks", + "datasetsTitle": "Evaluation Datasets", + "datasetsDescription": "Manage and view all generated evaluation test questions", + "tasksTitle": "Evaluation Tasks", + "tasksComingSoon": "Coming Soon", + "tasksComingSoonHint": "Evaluation task feature is under development", + "totalQuestions": "Total Questions", + "questionType": "Type", + "question": "Question", + "answer": "Answer", + "options": "Options", + "correct": "Correct", + "wrong": "Wrong", + "sourceChunk": "Source Chunk", + "tags": "Tags", + "tagsPlaceholder": "Enter tags, separated by commas", + "note": "Note", + "detail": "Detail", + "notFound": "Question not found", + "noData": "No evaluation data", + "noDataHint": "Please generate evaluation test set from text split page first", + "searchPlaceholder": "Search question content...", + "cardView": "Card View", + "listView": "List View", + "deleteSelected": "Delete Selected ({{count}})", + "deleteConfirmTitle": "Confirm Delete", + "deleteConfirmMessage": "Are you sure you want to delete {{count}} question(s)? This action cannot be undone.", + "questionTypes": { + "true_false": "True/False", + "single_choice": "Single Choice", + "multiple_choice": "Multiple Choice", + "short_answer": "Short Answer", + "open_ended": "Open Ended" + } + }, + "evalDatasets": { + "import": { + "title": "Import Eval Datasets", + "questionType": "Question Type", + "selectTypeFirst": "Please select a question type first", + "selectFile": "Please select a file to import", + "invalidFileType": "Unsupported file format, please upload json, xls or xlsx files", + "formatPreview": "Data Format Preview", + "downloadTemplate": "Download Template", + "template": "Template", + "uploadFile": "Upload File", + "dropOrClick": "Click or drag file here", + "supportedFormats": "Supports JSON, XLS, XLSX formats", + "tags": "Tags (Optional)", + "tagsPlaceholder": "Add tags for imported data, separate multiple tags with commas", + "tagsHelp": "All imported data will be tagged with these labels", + "import": "Import", + "importing": "Importing...", + "failed": "Import failed", + "success": "Import successful", + "successMessage": "Successfully imported {{count}} evaluation datasets", + "showingErrors": "Showing first {{count}} errors", + "custom": "Custom Import", + "builtin": "Built-in Datasets", + "builtinTitle": "Select Built-in Dataset", + "searchPlaceholder": "Search datasets...", + "confirmImportTitle": "Confirm Import", + "confirmImportMessage": "Are you sure you want to import dataset \"{{name}}\"? This will add new evaluation data to the current project.", + "downloading": "Downloading..." + }, + "export": { + "title": "Export Eval Datasets", + "formatLabel": "Export Format", + "filterLabel": "Filter Criteria", + "previewLabel": "Data to export: ", + "records": " records", + "largeDataHint": "Large dataset, streaming export will be used, please wait", + "exporting": "Exporting...", + "exportBtn": "Export", + "jsonDesc": "Standard JSON array", + "jsonlDesc": "One record per line", + "csvDesc": "Table format", + "noTagsAvailable": "No tags available" + } + }, + "evalTasks": { + "title": "Model Evaluation Tasks", + "createTitle": "Create Evaluation Task", + "detailTitle": "Evaluation Task Details", + "createTask": "Create Task", + "noTasks": "No evaluation tasks", + "noTasksHint": "Create an evaluation task to test model performance on evaluation datasets", + "selectModels": "Select Test Models", + "selectModelsHint": "You can select multiple models for comparison", + "selectJudgeModel": "Select Judge Model", + "selectJudgeModelPlaceholder": "Please select...", + "selectJudgeModelHint": "Judge model is used to score subjective questions and cannot be the same as test models", + "judgeModel": "Judge Model", + "filterByType": "Filter by Question Type", + "filterByTypeHint": "Leave empty to use all questions", + "selectedQuestions": "Selected Questions", + "questions": "questions", + "hasSubjectiveHint": "Contains subjective questions (short answer/open-ended), a judge model is required", + "hasSubjective": "Has Subjective", + "startEval": "Start Evaluation", + "progress": "Progress", + "totalQuestions": "Questions", + "status": "Status", + "totalScore": "Total Score", + "correctCount": "Correct", + "accuracy": "Accuracy", + "statsByType": "Statistics by Type", + "resultDetails": "Result Details", + "question": "Question", + "questionType": "Type", + "result": "Result", + "score": "Score", + "correctAnswer": "Correct Answer", + "modelAnswer": "Model Answer", + "judgeResponse": "Judge Response", + "interrupt": "Interrupt", + "statusProcessing": "Processing", + "statusCompleted": "Completed", + "statusFailed": "Failed", + "statusInterrupted": "Interrupted", + "deleteConfirmTitle": "Confirm Delete", + "deleteConfirmMessage": "Are you sure you want to delete this evaluation task? All results will also be deleted.", + "interruptConfirmTitle": "Confirm Interrupt", + "interruptConfirmMessage": "Are you sure you want to interrupt this task? Completed results will be preserved.", + "errorNoModels": "Please select at least one test model", + "errorNoQuestions": "No evaluation questions available", + "errorNoJudgeModel": "Subjective questions exist, please select a judge model", + "errorJudgeSameAsTest": "Judge model cannot be the same as test models", + "errorCreateFailed": "Failed to create evaluation task", + "errorLoadFailed": "Failed to load evaluation tasks", + "errorDeleteFailed": "Failed to delete evaluation task", + "errorInterruptFailed": "Failed to interrupt evaluation task", + "statusSuccess": "Success", + "statusFormatError": "Format Error", + "statusApiError": "API Error", + "statusUnknown": "Unknown Status", + "duration": "Duration", + "answerStatus": "Answer Status", + "modelInfo": "Model Info", + "reportTitle": "Model Evaluation Report", + "taskIdLabel": "Task ID", + "pageInfo": "Page: {{page}} / {{totalPages}}", + "noMatchingResults": "No evaluation results match the current filters", + "reportFooter": "Easy Dataset Evaluation System · Generated by AI", + "finalSelection": "Final Selection: ", + "questionsSuffix": " questions", + "noModelsAvailable": "No models available, please configure models in settings first", + "filterTitle": "Question Filter", + "clearFilter": "Clear Filter", + "searchKeyword": "Search Keyword", + "searchPlaceholder": "Search question or answer content...", + "filterByTypeLabel": "Filter by Type", + "filterByTagLabel": "Filter by Tag", + "questionCountLabel": "Question Count: ", + "useAllQuestions": "Use all filtered results", + "randomSampleHint": "Randomly sample {{questionCount}} from {{filteredCount}} questions", + "durationFormat": "({{time}}s)", + "totalQuestionsLabel": "Total", + "correctLabel": "Correct", + "incorrectLabel": "Incorrect", + "judgeComment": "AI Judge Comment:", + "scoreUnit": " pts", + "shortAnswer": "Short Answer", + "openEnded": "Open-ended", + "scoreAnchorsTitle": "{{type}} Scoring Rules", + "customizable": "Customizable", + "scoreAnchorsHint": "Customize scoring criteria to guide LLM in evaluating model responses", + "restoreDefault": "Restore Default", + "scoreRange": "Score Range", + "scoreDescriptionPlaceholder": "Enter scoring criteria description for this range..." + }, + "blindTest": { + "title": "Human Blind Test", + "createTitle": "Create Blind Test", + "createTask": "Create Task", + "noTasks": "No blind test tasks", + "noTasksHint": "Create a blind test task to compare two models' answer quality", + "selectModels": "Select Models to Compare", + "modelA": "Model A", + "modelB": "Model B", + "modelComparison": "Model Comparison", + "selectQuestions": "Select Test Questions", + "questionType": "Question Type", + "questionTypeHint": "Blind test only supports short answer and open-ended questions", + "filterByTag": "Filter by Tag", + "questionCount": "Question Count", + "availableQuestions": "Available: {{count}} questions", + "useAllQuestions": "Use all filtered results", + "randomSample": "Randomly sample {{count}} questions", + "startBlindTest": "Start Blind Test", + "creating": "Creating...", + "noModelsAvailable": "No models available, please configure models in settings first", + "errorSelectModelA": "Please select Model A", + "errorSelectModelB": "Please select Model B", + "errorSameModel": "Two models cannot be the same", + "errorNoQuestions": "No questions match the criteria", + "statusProcessing": "In Progress", + "statusCompleted": "Completed", + "statusFailed": "Failed", + "statusInterrupted": "Interrupted", + "progress": "Progress", + "viewDetails": "View Details", + "continue": "Continue Test", + "interrupt": "Interrupt Task", + "deleteConfirmTitle": "Confirm Delete", + "deleteConfirmMessage": "Are you sure you want to delete this blind test task? This action cannot be undone.", + "interruptConfirmTitle": "Confirm Interrupt", + "interruptConfirmMessage": "Are you sure you want to interrupt this task? Completed results will be preserved.", + "inProgress": "Blind Test In Progress", + "generatingAnswers": "Generating answers...", + "question": "Question", + "answerA": "Answer A", + "answerB": "Answer B", + "duration": "Duration", + "whichBetter": "Which answer is better?", + "leftBetter": "Left is Better", + "rightBetter": "Right is Better", + "bothGood": "Both Good", + "bothBad": "Both Bad", + "loadQuestion": "Load Question", + "taskNotFound": "Task not found", + "resultTitle": "Blind Test Results", + "resultSummary": "Result Summary", + "wins": "Wins", + "times": "times", + "totalQuestions": "Total Questions", + "ties": "Ties", + "detailResults": "Detailed Results", + "left": "Left", + "right": "Right" + } +} diff --git a/easy-dataset-main/locales/pt-BR/translation.json b/easy-dataset-main/locales/pt-BR/translation.json new file mode 100644 index 0000000..86a9a89 --- /dev/null +++ b/easy-dataset-main/locales/pt-BR/translation.json @@ -0,0 +1,1854 @@ +{ + "language": { + "switchToEnglish": "Mudar para Inglês", + "switchToChinese": "Mudar para Chinês", + "switcherTitle": "Mudar Idioma / Change Language / Dil Değiştir", + "english": "Inglês", + "chineseSimplified": "Chinês Simplificado", + "turkish": "Turco", + "en": "EN", + "zh": "中" + }, + "theme": { + "switchToLight": "Mudar para Modo Claro", + "switchToDark": "Mudar para Modo Escuro" + }, + "settings": { + "promptConfig": "Configuração de Prompts", + "promptsDescription": "Configure vários prompts personalizados usados no projeto para intervenção manual na geração do conjunto de dados.", + "globalPrompt": "Prompt Global", + "questionPrompt": "Prompt de Geração de Perguntas", + "answerPrompt": "Prompt de Geração de Respostas", + "labelPrompt": "Prompt de Rotulação de Perguntas", + "domainTreePrompt": "Prompt de Construção da Árvore de Domínio", + "globalPromptPlaceholder": "Por favor, insira o prompt global (use com cautela, pode afetar a geração geral)", + "questionPromptPlaceholder": "Por favor, insira o prompt personalizado para geração de perguntas", + "answerPromptPlaceholder": "Por favor, insira o prompt personalizado para geração de respostas", + "labelPromptPlaceholder": "Por favor, insira o prompt personalizado para rotulação de perguntas (configuração temporariamente não suportada)", + "domainTreePromptPlaceholder": "Por favor, insira o prompt personalizado para construção da árvore de domínio", + "cleanPrompt": "Prompt de Limpeza de Dados", + "cleanPromptPlaceholder": "Por favor, insira o prompt personalizado para limpeza de dados", + "loadPromptsFailed": "Falha ao carregar configuração de prompts", + "savePromptsSuccess": "Configuração de prompts salva com sucesso", + "savePromptsFailed": "Falha ao salvar configuração de prompts", + "title": "Configurações do Projeto", + "basicInfo": "Informações Básicas", + "modelConfig": "Configuração do Modelo", + "taskConfig": "Configuração de Tarefas", + "tabsAriaLabel": "Abas de Configurações", + "idNotEditable": "ID do Projeto não pode ser editado", + "saveBasicInfo": "Salvar Informações Básicas", + "saveSuccess": "Salvo com sucesso", + "saveFailed": "Falha ao salvar", + "deleteSuccess": "Excluído com sucesso", + "deleteFailed": "Falha ao excluir", + "fetchTasksFailed": "Falha ao obter configuração de tarefas", + "saveTasksFailed": "Falha ao salvar configuração de tarefas", + "textSplitSettings": "Configurações de Divisão de Texto", + "minLength": "Comprimento Mínimo", + "maxLength": "Comprimento Máximo de Divisão", + "textSplitDescription": "Ajuste o intervalo de comprimento da divisão de texto, afeta a granularidade do resultado da divisão", + "splitType": "Estratégia de Divisão", + "splitTypeMarkdown": "Divisão por Estrutura de Documento (Markdown)", + "splitTypeMarkdownDesc": "Divide texto automaticamente com base nos títulos do documento, mantendo a integridade semântica, adequado para documentos Markdown com estrutura clara", + "splitTypeRecursive": "Divisão por Estrutura de Texto (Separadores Personalizados)", + "splitTypeRecursiveDesc": "Tenta recursivamente separadores de múltiplos níveis (configuráveis), usando primeiro separadores de alta prioridade, depois separadores secundários, adequado para documentos complexos", + "splitTypeText": "Divisão de Comprimento Fixo (Caracteres)", + "splitTypeTextDesc": "Divide texto pelo separador especificado (configurável), depois combina pelo comprimento especificado, adequado para arquivos de texto comuns", + "splitTypeToken": "Divisão de Comprimento Fixo (Token)", + "splitTypeTokenDesc": "Divide com base na contagem de Tokens (não caracteres)", + "splitTypeCode": "Divisão Inteligente de Código de Programa", + "splitTypeCodeDesc": "Realiza divisão inteligente com base na estrutura sintática de diferentes linguagens de programação, evitando divisões em locais com sintaxe incompleta", + "splitTypeCustom": "Divisão por Símbolos Personalizados", + "splitTypeCustomDesc": "Divide documentos com base em símbolos personalizados, os separadores serão descartados, os blocos de texto divididos não são afetados pelo tamanho do bloco", + "codeLanguage": "Linguagem de Código", + "codeLanguageHelper": "Selecione a linguagem de código para divisão, a divisão inteligente será realizada de acordo com as características da linguagem", + "chunkSize": "Tamanho do Bloco", + "chunkOverlap": "Comprimento de Sobreposição do Bloco", + "separator": "Separador", + "separatorHelper": "Separador usado para dividir texto, como \n\n representa linha em branco", + "customSeparator": "Separador Personalizado", + "customSeparatorHelper": "Separador personalizado usado para dividir texto, como --- ou ===", + "separators": "Lista de Separadores", + "separatorsInput": "Separadores (separados por vírgula)", + "separatorsHelper": "Lista de separadores separados por vírgula, ordenados por prioridade", + "questionGenSettings": "Configurações de Geração de Perguntas", + "questionGenLength": "Gerar uma pergunta a cada {{length}} caracteres", + "questionMaskRemovingProbability": "Remover {{probability}}% dos pontos de interrogação no final das perguntas", + "questionGenDescription": "Definir o comprimento máximo para geração de perguntas", + "concurrencyLimit": "Limite de Concorrência", + "concurrencyLimitHelper": "Limitar a quantidade de tarefas simultâneas para geração de perguntas e conjunto de dados", + "saveTaskConfig": "Salvar Configuração de Tarefas", + "pdfSettings": "Configuração de Conversão de Arquivos PDF", + "minerUToken": "Configuração de Token para Conversão de PDF (MinerU API)", + "minerUHelper": "O Token do MinerU tem validade de apenas 14 dias, por favor, substitua o Token a tempo", + "minerULocalUrl": "Configuração de URL para Conversão de PDF (MinerU Local)", + "vision": "Seleção de Modelo de Visão Personalizado", + "visionConcurrencyLimit": "Limite de Concorrência do Modelo de Visão", + "huggingfaceSettings": "Configurações do Hugging Face", + "huggingfaceToken": "Token do Hugging Face", + "multiTurnSettings": "Configurações de Conjunto de Dados de Diálogo de Múltiplas Rodadas", + "multiTurnSystemPrompt": "Prompt do Sistema", + "multiTurnSystemPromptHelper": "Definir a identidade e normas de comportamento do assistente de IA", + "multiTurnScenario": "Cenário de Diálogo", + "multiTurnScenarioHelper": "Descrever o cenário específico e objetivo do diálogo", + "multiTurnRounds": "Número de Rodadas de Diálogo: {{rounds}} rodadas", + "multiTurnRoleA": "Configuração do Personagem A (Usuário)", + "multiTurnRoleAHelper": "Definir a identidade e características do papel do usuário", + "multiTurnRoleB": "Configuração do Personagem B (Assistente)", + "multiTurnRoleBHelper": "Definir a identidade e características do papel do assistente", + "multiTurnDescription": "A configuração de diálogo de múltiplas rodadas é usada para gerar conjuntos de dados de diálogo coerentes de múltiplas rodadas, suporta personalização de personagens e cenários", + "evalQuestionSettings": "Configurações de Geração de Conjunto de Teste", + "evalQuestionSettingsDescription": "Configure a proporção de cada tipo de questão ao gerar conjunto de teste, proporção 0 significa não gerar esse tipo de questão", + "evalTrueFalseRatio": "Proporção de Questões de Verdadeiro/Falso", + "evalSingleChoiceRatio": "Proporção de Questões de Múltipla Escolha", + "evalMultipleChoiceRatio": "Proporção de Questões de Múltipla Escolha (Várias Respostas)", + "evalShortAnswerRatio": "Proporção de Respostas Curtas Fixas", + "evalOpenEndedRatio": "Proporção de Respostas Abertas", + "evalQuestionRatioHelper": "O sistema alocará automaticamente a quantidade de geração de cada tipo de questão de acordo com as proporções definidas, a soma de todas as proporções não precisa ser igual a um valor específico", + "prompts": { + "selectPromptFirst": "Por favor, selecione um prompt à esquerda", + "customized": "Personalizado", + "editPrompt": "Editar Prompt", + "restoreDefault": "Restaurar Padrão", + "promptType": "Tipo de Prompt", + "keyName": "Nome da Chave", + "contentPlaceholder": "Por favor, insira o conteúdo do prompt personalizado...", + "restoreDefaultContent": "Restaurar conteúdo padrão", + "noPromptsAvailable": "Nenhum prompt disponível", + "restoreSuccess": "Restaurado para o prompt padrão", + "restoreFailed": "Falha ao restaurar o prompt padrão", + "deleteError": "Erro ao excluir prompt:", + "saveSuccess": "Prompt salvo com sucesso", + "saveFailed": "Falha ao salvar prompt", + "saveError": "Erro ao salvar prompt:", + "createCustomPrompt": "Criar Prompt Personalizado", + "fetchContentError": "Falha ao obter o conteúdo mais recente do prompt:" + } + }, + "questions": { + "autoGenerateDataset": "Gerar Conjunto de Dados Automaticamente", + "autoGenerateDatasetTip": "Criar tarefa de processamento em lote em segundo plano: consultar automaticamente perguntas pendentes de geração de respostas e gerar conjunto de dados", + "generateSingleTurnDataset": "Gerar Conjunto de Dados de Diálogo de Rodada Única", + "generateSingleTurnDatasetDesc": "Gerar conjunto de dados de perguntas e respostas com base nas perguntas", + "generateMultiTurnDataset": "Gerar Conjunto de Dados de Diálogo de Múltiplas Rodadas", + "generateImageDataset": "Gerar Conjunto de Dados de Perguntas e Respostas de Imagens", + "generateMultiTurnDatasetDesc": "Gerar conjunto de dados de diálogo de múltiplas rodadas com base nas perguntas", + "multiTurnNotConfigured": "Por favor, configure primeiro os parâmetros relacionados a diálogo de múltiplas rodadas nas configurações do projeto", + "filterAll": "Todas as Perguntas", + "filterAnswered": "Respostas Geradas", + "filterUnanswered": "Respostas Não Geradas", + "filterChunkNamePlaceholder": "Filtrar por nome do bloco de texto...", + "sourceTypeAll": "Todas as Fontes de Dados", + "sourceTypeText": "Fonte de Dados de Texto", + "sourceTypeImage": "Fonte de Dados de Imagem", + "title": "Perguntas", + "confirmDeleteTitle": "Confirmar Exclusão de Pergunta", + "confirmDeleteContent": "Tem certeza de que deseja excluir a pergunta \"{{question}}\"? Esta ação não pode ser desfeita.", + "deleting": "Excluindo pergunta...", + "batchDeleteTitle": "Confirmar Exclusão em Lote de Perguntas", + "batchDeleting": "Excluindo {{count}} perguntas...", + "deleteSuccess": "Pergunta excluída com sucesso", + "deleteFailed": "Falha ao excluir pergunta", + "batchDeleteSuccess": "{{count}} perguntas excluídas com sucesso", + "batchDeletePartial": "Exclusão concluída, sucesso: {{success}}, falha: {{failed}}", + "batchDeleteFailed": "Falha na exclusão em lote de perguntas", + "noQuestionsSelected": "Por favor, selecione perguntas primeiro", + "batchGenerateStart": "Iniciando geração de conjunto de dados para {{count}} perguntas", + "invalidQuestionKey": "Chave de pergunta inválida", + "listView": "Lista de Perguntas", + "treeView": "Visualização em Árvore de Domínio", + "selectAll": "Selecionar Todos", + "selectedCount": "{{count}} perguntas selecionadas", + "totalCount": "Total de {{count}} perguntas", + "searchPlaceholder": "Pesquisar perguntas ou tags...", + "searchMatch": "Correspondência", + "searchNotMatch": "Não Corresponde", + "deleteSelected": "Excluir Selecionados", + "batchGenerate": "Construir Conjunto de Dados em Lote", + "generating": "Gerando conjunto de dados", + "generatingProgress": "Concluído: {{completed}}/{{total}}", + "generatedCount": "{{count}} conjuntos de dados gerados", + "pleaseWait": "Por favor, aguarde...", + "selectAllLimitReached": "{{count}} perguntas selecionadas (limite máximo atingido)", + "selectAllFailed": "Falha na operação de seleção total, por favor, tente novamente mais tarde", + "createSuccess": "Pergunta criada com sucesso", + "updateSuccess": "Pergunta atualizada com sucesso", + "operationSuccess": "Operação bem-sucedida", + "operationFailed": "Falha na operação", + "editQuestion": "Editar Pergunta", + "questionContent": "Conteúdo da Pergunta", + "sourceType": "Tipo de Fonte de Dados", + "sourceType.text": "Texto", + "sourceType.image": "Imagem", + "selectChunk": "Selecionar Bloco de Texto", + "searchChunk": "Pesquisar bloco de texto...", + "selectImage": "Selecionar Imagem", + "searchImage": "Pesquisar imagem...", + "selectTag": "Selecionar Tag", + "searchTag": "Pesquisar tag...", + "createQuestion": "Criar Pergunta", + "createNormalQuestion": "Criar Pergunta Normal", + "createQuestionTemplate": "Criar Modelo de Pergunta", + "questionPlaceholder": "Por favor, insira o conteúdo da pergunta", + "noChunkSelected": "Por favor, selecione um bloco de texto primeiro", + "fetchTemplatesFailed": "Falha ao obter modelos de pergunta", + "createTemplateSuccess": "Modelo de pergunta criado com sucesso", + "createTemplateFailed": "Falha ao criar modelo de pergunta", + "updateTemplateSuccess": "Modelo de pergunta atualizado com sucesso", + "updateTemplateFailed": "Falha ao atualizar modelo de pergunta", + "deleteTemplateSuccess": "Modelo de pergunta excluído com sucesso", + "deleteTemplateFailed": "Falha ao excluir modelo de pergunta", + "exportQuestions": "Exportar Conjunto de Perguntas", + "exportScope": "Escopo de Exportação", + "exportAll": "Exportar Todos ({{count}} perguntas)", + "exportSelected": "Exportar Selecionados ({{count}} perguntas)", + "exportFormat": "Formato de Exportação", + "txtFormat": "Texto Puro (apenas conteúdo da pergunta)", + "exportSuccess": "Conjunto de perguntas exportado com sucesso", + "exportFailed": "Falha ao exportar conjunto de perguntas", + "template": { + "management": "Modelo de Pergunta", + "create": "Criar Modelo de Pergunta", + "edit": "Editar Modelo de Pergunta", + "question": "Conteúdo da Pergunta", + "description": "Prompt", + "descriptionHelp": "Usado para adicionar ao prompt geral quando a IA gerar respostas relacionadas a este modelo de pergunta posteriormente, para intervir no resultado final da geração de respostas", + "noTemplates": "Nenhum modelo de pergunta disponível, clique no botão criar para adicionar", + "deleteConfirm": "Tem certeza de que deseja excluir este modelo de pergunta?", + "used": "Usado", + "addLabel": "Adicionar Tag", + "customFormat": "Formato Personalizado", + "customFormatHelp": "Insira restrições de saída no formato JSON", + "customFormatInfo": "Este formato será fornecido como prompt ao modelo grande, usado para restringir o formato de saída", + "sourceTypeInfo": "Tipo de Fonte de Dados", + "sourceType": { + "label": "Tipo de Fonte de Dados", + "image": "Imagem (usado para gerar QA de imagens)", + "text": "Texto (usado para gerar QA de blocos de texto)" + }, + "answerType": { + "label": "Formato de Saída da Resposta", + "text": "Texto Normal", + "tags": "Array de Tags", + "customFormat": "Formato Personalizado" + }, + "errors": { + "questionRequired": "Por favor, insira o conteúdo da pergunta", + "labelsRequired": "Perguntas do tipo tag precisam de pelo menos uma tag", + "customFormatRequired": "Por favor, insira o formato personalizado", + "invalidJson": "Formato JSON incorreto" + }, + "autoGenerate": "Gerar perguntas automaticamente após criar modelo", + "autoGenerateHelpText": "Criará automaticamente perguntas baseadas neste modelo para todos os blocos de texto no projeto", + "autoGenerateHelpImage": "Criará automaticamente perguntas baseadas neste modelo para todas as imagens no projeto", + "confirmAutoGenerate": "Confirmar geração automática de perguntas", + "confirmAutoGenerateTextMessage": "Você selecionou gerar perguntas automaticamente. O sistema criará perguntas baseadas neste modelo para todos os blocos de texto no projeto.", + "confirmAutoGenerateImageMessage": "Você selecionou gerar perguntas automaticamente. O sistema criará perguntas baseadas neste modelo para todas as imagens no projeto.", + "autoGenerateWarning": "Esta operação pode criar um grande número de perguntas, por favor, confirme antes de continuar.", + "autoGenerateSuccess": "Perguntas criadas com sucesso para {{count}} fontes de dados", + "autoGeneratePartialFail": "{{success}} perguntas criadas com sucesso, {{fail}} falhas", + "autoGenerateFailed": "Falha na geração automática de perguntas" + }, + "noTagSelected": "Por favor, selecione uma tag", + "deleteConfirm": "Tem certeza de que deseja excluir esta pergunta?", + "generateMultiTurn": "Gerar Diálogo de Múltiplas Rodadas", + "multiTurnGenerated": "Conjunto de dados de diálogo de múltiplas rodadas gerado com sucesso!" + }, + "common": { + "dataSource": "Fonte de Dados", + "menu": "Menu", + "openMenu": "Abrir Menu de Navegação", + "all": "Todos", + "jumpTo": "Ir para", + "unknownError": "Erro Desconhecido", + "create": "Criar", + "confirm": "Confirmar", + "edit": "Editar", + "delete": "Excluir", + "save": "Salvar", + "cancel": "Cancelar", + "complete": "Concluir", + "close": "Fechar", + "add": "Adicionar", + "remove": "Remover", + "loading": "Carregando...", + "yes": "Sim", + "no": "Não", + "confirmDelete": "Confirmar exclusão? Esta ação não pode ser desfeita!", + "saving": "Salvando...", + "deleting": "Excluindo...", + "actions": "Ações", + "confirmDeleteDataSet": "Confirmar exclusão do conjunto de dados? A operação não pode ser desfeita!", + "noData": "Nenhum", + "failed": "Falhou", + "success": "Sucesso", + "backToList": "Voltar para Lista", + "label": "Tag", + "confirmDeleteDescription": "Confirmar exclusão? Esta ação não pode ser recuperada.", + "more": "Mais", + "import": "Importar", + "export": "Exportar", + "fetchError": "Erro ao obter dados", + "confirmDeleteQuestion": "Confirmar exclusão desta pergunta? Esta ação não pode ser recuperada.", + "deleteSuccess": "Excluído com sucesso", + "visitGitHub": "Visitar Repositório GitHub", + "syncOldData": "Sincronizar Dados de Arquivos", + "copy": "Copiar", + "copied": "Copiado", + "enabled": "Ativado", + "disabled": "Desativado", + "generating": "Gerando...", + "processing": "Processando...", + "items": "itens", + "detailInfo": "Informações Detalhadas", + "reset": "Redefinir", + "apply": "Aplicar", + "mainNavigation": "Navegação Principal", + "goHome": "Voltar para Início", + "goToHomePage": "Voltar para Página Inicial", + "mobileNavigation": "Menu de Navegação Móvel", + "navigation": "Navegação", + "closeMenu": "Fechar Menu", + "documentation": "Documentação", + "viewOnGitHub": "Ver no GitHub", + "back": "Voltar", + "refresh": "Atualizar", + "expand": "Expandir Todos", + "collapse": "Recolher Conteúdo" + }, + "home": { + "title": "Easy Dataset", + "subtitle": "Uma poderosa ferramenta de criação de conjunto de dados para ajuste fino de grandes modelos de linguagem", + "createProject": "Criar Projeto", + "searchDataset": "Pesquisar Conjunto de Dados Público" + }, + "projects": { + "reuseConfig": "Reutilizar Configuração do Modelo", + "noReuse": "Não Reutilizar Configuração", + "selectProject": "Selecionar Projeto", + "fetchFailed": "Falha ao obter lista de projetos", + "fetchError": "Erro ao obter lista de projetos", + "loading": "Carregando seus projetos...", + "createFailed": "Falha ao criar projeto", + "createError": "Erro ao criar projeto", + "createNew": "Criar Novo Projeto", + "saveFailed": "Falha ao salvar projeto", + "id": "ID do Projeto", + "name": "Nome do Projeto", + "description": "Descrição do Projeto", + "questions": "Perguntas", + "datasets": "Conjuntos de Dados", + "evalDatasets": "Conjuntos de Avaliação", + "tokens": "Tokens", + "lastUpdated": "Última Atualização", + "viewDetails": "Ver Detalhes", + "createFirst": "Criar Primeiro Projeto", + "noProjects": "Nenhum Projeto", + "notExist": "Projeto não existe", + "createProject": "Criar Projeto", + "deleteConfirm": "Confirmar exclusão do projeto? Esta ação não pode ser recuperada.", + "deleteSuccess": "Projeto excluído com sucesso", + "deleteFailed": "Falha ao excluir projeto", + "backToHome": "Voltar para Início", + "deleteConfirmTitle": "Confirmar Exclusão do Projeto", + "title": "Gerenciamento de Projetos", + "openDirectory": "Abrir Diretório do Projeto" + }, + "textSplit": { + "dragToUpload": "Arraste arquivos para cá para fazer upload", + "fileList": "Lista de Arquivos", + "autoGenerateQuestions": "Extrair Perguntas Automaticamente", + "autoGenerateQuestionsTip": "Criar tarefa de processamento em lote em segundo plano: consultar automaticamente blocos de texto pendentes de geração de perguntas e extrair perguntas", + "exportChunks": "Exportar Blocos de Texto", + "allChunks": "Todos os Blocos de Texto", + "generatedQuestions2": "Perguntas Geradas", + "ungeneratedQuestions": "Perguntas Não Geradas", + "contentKeyword": "Conteúdo do Bloco de Texto", + "contentKeywordPlaceholder": "Insira palavras-chave para pesquisar conteúdo do bloco de texto", + "characterRange": "Intervalo de Caracteres", + "questionStatus": "Status da Pergunta", + "noFilesUploaded": "Nenhum arquivo enviado ainda", + "unknownFile": "Arquivo Desconhecido", + "fetchFilesFailed": "Erro ao obter lista de arquivos", + "editTag": "Editar Tag", + "deleteTag": "Excluir Tag", + "addTag": "Adicionar Tag", + "selectedCount": "{{count}} blocos de texto selecionados", + "totalCount": "Total de {{count}} blocos de texto", + "batchGenerateQuestions": "Gerar Perguntas em Lote", + "batchDeleteChunks": "Excluir em Lote", + "batchDeleteChunksConfirmTitle": "Confirmar Exclusão em Lote", + "batchDeleteChunksConfirmMessage": "Tem certeza de que deseja excluir os {{count}} blocos de texto selecionados? Esta ação não pode ser desfeita.", + "uploadedDocuments": "{{count}} documentos enviados", + "title": "Processamento de Arquivos", + "uploadNewDocument": "Enviar Novo Arquivo", + "selectFile": "Selecionar Arquivo (suporta múltiplos)", + "markdownOnly": "Atualmente suporta apenas arquivos no formato Markdown (.md) (recomenda-se enviar arquivos do mesmo domínio)", + "supportedFormats": "Formatos suportados: .pdf .md, .txt, .docx (recomenda-se enviar arquivos do mesmo domínio)", + "uploadAndProcess": "Enviar e Processar Arquivo", + "selectedFiles": "Arquivos Selecionados ({{count}})", + "oneFileMessage": "Um projeto limita o processamento a um arquivo, se precisar enviar um novo arquivo, por favor, exclua o arquivo existente primeiro", + "mutilFileMessage": "A árvore de domínio será reconstruída após enviar novo arquivo", + "noChunks": "Nenhum bloco de texto ainda, por favor, envie e processe o arquivo primeiro", + "chunkDetails": "Detalhes do Bloco de Texto: {{chunkId}}", + "fetchChunksFailed": "Falha ao obter blocos de texto", + "fetchChunksError": "Erro ao obter blocos de texto", + "fileResultReceived": "Resultado do arquivo recebido", + "fileUploadSuccess": "Arquivo enviado com sucesso", + "splitTextFailed": "Falha na divisão de texto", + "splitTextError": "Erro na divisão de texto", + "deleteChunkFailed": "Falha ao excluir bloco de texto", + "deleteChunkError": "Erro ao excluir bloco de texto", + "selectModelFirst": "Por favor, selecione um modelo primeiro, pode selecionar na barra de navegação superior", + "modelNotAvailable": "O modelo selecionado não está disponível, por favor, selecione novamente", + "generateQuestionsFailed": "Falha ao gerar perguntas para o bloco de texto {{chunkId}}", + "questionsGenerated": "{{total}} perguntas geradas", + "customSplitMode": "Modo de Divisão Personalizado", + "customSplitInstructions": "Selecione o conteúdo do texto para adicionar pontos de divisão. O sistema adicionará marcadores de divisão na posição selecionada.", + "splitPointsList": "Pontos de Divisão Adicionados", + "saveSplitPoints": "Salvar Pontos de Divisão", + "confirmCustomSplitTitle": "Confirmar Substituição da Divisão Original", + "confirmCustomSplitMessage": "Atenção: A divisão personalizada substituirá o resultado da divisão automática anterior deste arquivo. Tem certeza de que deseja continuar salvando?", + "customSplitSuccess": "Divisão personalizada salva com sucesso", + "customSplitFailed": "Falha ao salvar divisão personalizada", + "missingRequiredData": "Dados necessários ausentes", + "chunksPreview": "Pré-visualização do Tamanho dos Blocos", + "chunk": "Bloco de Texto", + "characters": "caracteres", + "questionsGeneratedSuccess": "{{total}} perguntas geradas com sucesso para o bloco de texto", + "generateQuestionsForChunkFailed": "Falha ao gerar perguntas para o bloco de texto {{chunkId}}", + "generateQuestionsForChunkError": "Erro ao gerar perguntas para o bloco de texto {{chunkId}}", + "generateQuestionsError": "Erro ao gerar perguntas", + "partialSuccess": "Geração de perguntas parcialmente bem-sucedida para blocos de texto ({{successCount}}/{{total}}), {{errorCount}} blocos de texto falharam", + "allSuccess": "{{totalQuestions}} perguntas geradas com sucesso para {{successCount}} blocos de texto", + "fileDeleted": "Arquivo {{fileName}} excluído, atualizando lista de blocos de texto", + "tabs": { + "smartSplit": "Divisão Inteligente", + "domainAnalysis": "Análise de Domínio" + }, + "loading": "Carregando...", + "fetchingDocuments": "Obtendo dados de arquivos", + "processing": "Processando...", + "progressStatus": "{{total}} blocos de texto selecionados, {{completed}} processados", + "processingPleaseWait": "Processando, por favor, aguarde!", + "oneFileLimit": "Arquivo já enviado, não é permitido selecionar novo arquivo", + "unsupportedFormat": "Formato de arquivo não suportado: {{files}}", + "modelInfoParseError": "Falha ao analisar informações do modelo", + "uploadFailed": "Falha no upload, por favor, atualize a página e tente novamente!", + "uploadSuccess": "{{count}} arquivo(s) enviado(s) com sucesso", + "deleteFailed": "Falha ao excluir arquivo", + "deleteSuccess": "Arquivo {{fileName}} excluído com sucesso", + "generatedQuestions": "{{count}} perguntas geradas", + "generatedEvalQuestions": "{{count}} questões de teste geradas", + "generateQuestions": "Gerar Perguntas", + "generateEvalQuestions": "Gerar Conjunto de Teste", + "evalQuestionsGeneratedSuccess": "{{total}} questões de avaliação geradas com sucesso", + "generateEvalQuestionsFailed": "Falha ao gerar questões de avaliação", + "dataCleaning": "Limpeza de Dados", + "batchDataCleaning": "Limpeza de Dados em Lote", + "autoDataCleaning": "Limpeza de Dados Automática", + "autoDataCleaningTip": "Criar tarefa de processamento em lote em segundo plano: limpar automaticamente todos os blocos de texto", + "autoEvalGeneration": "Geração Automática de Conjunto de Avaliação", + "autoEvalGenerationTip": "Criar tarefa de processamento em lote em segundo plano: gerar automaticamente conjunto de dados de avaliação para todos os blocos de texto sem questões de avaliação geradas", + "autoTasks": "Tarefas Automáticas", + "dataCleaningSuccess": "Limpeza de dados concluída, comprimento original: {{originalLength}}, comprimento após limpeza: {{cleanedLength}}", + "dataCleaningFailed": "Falha na limpeza de dados para o bloco de texto {{chunkId}}", + "dataCleaningForChunkSuccess": "Limpeza de dados concluída para o bloco de texto {{chunkId}}", + "dataCleaningForChunkFailed": "Falha na limpeza de dados para o bloco de texto {{chunkId}}", + "dataCleaningForChunkError": "Erro na limpeza de dados para o bloco de texto {{chunkId}}", + "dataCleaningPartialSuccess": "Limpeza de dados parcialmente bem-sucedida para blocos de texto ({{successCount}}/{{total}}), {{errorCount}} blocos de texto falharam", + "dataCleaningAllSuccess": "Limpeza de dados concluída com sucesso para {{successCount}} blocos de texto", + "charsCount": "caracteres", + "pdfProcess": "Arquivo PDF detectado, por favor, selecione o método de processamento do arquivo PDF!", + "pdfProcessStatus": "Total de {{total}} arquivo(s), {{completed}} processado(s)", + "pdfPageProcessStatus": "Processando {{fileName}} total de {{total}} páginas, {{completed}} páginas convertidas", + "pdfProcessing": "Convertendo arquivo...", + "pdfProcessingFailed": "Falha no processamento do arquivo!", + "selectPdfProcessingStrategy": "Por favor, selecione o método de processamento do arquivo PDF:", + "pdfProcessingStrategyDefault": "Padrão", + "pdfProcessingStrategyDefaultHelper": "Usar estratégia de análise de PDF integrada", + "pdfProcessingStrategyMinerUHelper": "Usar análise MinerU API, por favor, configure o Token da API MinerU primeiro", + "pdfProcessingStrategyVision": "Modelo de Visão Personalizado", + "pdfProcessingStrategyVisionHelper": "Usar modelo de visão personalizado para análise", + "pdfProcessingToast": "Arquivo enviado com sucesso, o sistema criará tarefa em segundo plano para analisar o arquivo!", + "pdfProcessingLoading": "Executando tarefa de processamento de arquivo, por favor, aguarde a conclusão da tarefa antes de enviar novo arquivo...", + "pdfProcessingWaring": "Executando tarefa de processamento de arquivo, recomenda-se aguardar a conclusão da tarefa antes de realizar outras operações, caso contrário, pode afetar a qualidade da geração de dados!", + "basicPdfParsing": "Análise Básica de PDF", + "basicPdfParsingDesc": "Pode reconhecer arquivos PDF simples, incluindo estrutura de diretório principal, velocidade mais rápida", + "mineruApiDesc": "Pode reconhecer arquivos PDF complexos, incluindo fórmulas, gráficos (necessita configurar MinerU API Key)", + "mineruLocalDesc": "Pode reconhecer arquivos PDF complexos, incluindo fórmulas, gráficos (necessita configurar MinerU Local URL)", + "mineruApiDescDisabled": "Por favor, vá para [Configuração do Projeto - Configuração de Tarefas] para definir o Token do MinerU primeiro", + "mineruLocalDisabled": "Por favor, vá para [Configuração do Projeto - Configuração de Tarefas] para definir o URL do MinerU Local primeiro", + "mineruWebPlatform": "Análise da Plataforma Online MinerU", + "mineruWebPlatformDesc": "Pode reconhecer arquivos PDF complexos, incluindo fórmulas, gráficos (necessita ir para outro site)", + "mineruSelected": "MinerU selecionado para análise de PDF", + "mineruLocalSelected": "MinerU Local selecionado para análise de PDF", + "customVisionModel": "Análise por Modelo de Visão Personalizado", + "customVisionModelDesc": "Pode reconhecer arquivos PDF complexos, incluindo fórmulas, gráficos (necessita adicionar configuração de modelo de visão na configuração do modelo)", + "customVisionModelSelected": "Modelo de visão grande {{name}} ({{provider}}) selecionado para análise de PDF", + "defaultSelected": "Estratégia padrão integrada selecionada para análise de PDF", + "download": "Baixar Arquivo", + "deleteFile": "Excluir Arquivo", + "batchDelete": "Excluir em Lote ({{count}})", + "batchDeleteTitle": "Exclusão em Lote de Arquivos", + "batchDeleteConfirm": "Tem certeza de que deseja excluir os {{count}} arquivos selecionados? Esta ação não pode ser desfeita.", + "batchDeleteSuccess": "{{count}} arquivo(s) excluído(s) com sucesso", + "batchDeleteFailed": "Falha na exclusão em lote", + "searchFiles": "Pesquisar nome do arquivo...", + "searchResults": "{{count}} arquivo(s) encontrado(s) (total de {{total}})", + "noSearchResults": "Nenhum arquivo contendo \"{{searchTerm}}\" encontrado", + "noResultsOnCurrentPage": "Nenhum resultado de pesquisa na página atual, por favor, volte para a primeira página para visualizar", + "noDataOnCurrentPage": "Nenhum dado na página atual", + "viewChunk": "Ver Bloco de Texto", + "editChunk": "Editar Bloco de Texto {{chunkId}}", + "editChunkSuccess": "Bloco de texto editado com sucesso", + "editChunkFailed": "Falha ao editar bloco de texto", + "editChunkError": "Erro ao editar bloco de texto", + "deleteFileWarning": "Aviso: A exclusão do arquivo também excluirá o seguinte conteúdo relacionado", + "deleteFileWarningChunks": "Todos os blocos de texto associados", + "deleteFileWarningQuestions": "Todas as perguntas geradas pelos blocos de texto", + "deleteFileWarningDatasets": "Todos os conjuntos de dados gerados pelas perguntas", + "domainTree": { + "firstUploadTitle": "Geração da Árvore de Domínio", + "uploadTitle": "Envio de Arquivo - Processamento da Árvore de Domínio", + "deleteTitle": "Exclusão de Arquivo - Processamento da Árvore de Domínio", + "reviseOption": "Revisar Árvore de Domínio", + "reviseDesc": "Corrigir a árvore de domínio atual com base nas informações de arquivos adicionados ou excluídos, afetando apenas as partes alteradas", + "rebuildOption": "Reconstruir Árvore de Domínio", + "rebuildDesc": "Regenerar a árvore de domínio completa com base nas informações de diretório de todos os arquivos", + "keepOption": "Manter Inalterado", + "keepDesc": "Manter a estrutura atual da árvore de domínio inalterada, sem fazer modificações" + } + }, + "domain": { + "title": "Árvore de Conhecimento de Domínio", + "addRootTag": "Adicionar Tag de Primeiro Nível", + "addFirstTag": "Adicionar Primeira Tag", + "noTags": "Nenhum dado de árvore de tags de domínio disponível", + "docStructure": "Estrutura de Diretório do Documento", + "noToc": "Nenhuma estrutura de diretório disponível, por favor, envie e processe o arquivo primeiro", + "editTag": "Editar Tag", + "deleteTag": "Excluir Tag", + "addChildTag": "Adicionar Tag Filha", + "deleteTagConfirmTitle": "Excluir Tag", + "deleteTagConfirmMessage": "Tem certeza de que deseja excluir a tag \"{{tag}}\"?", + "deleteWarning": "Esta ação excluirá esta tag e todas as suas tags filhas, perguntas e conjuntos de dados, e não poderá ser recuperada!", + "dialog": { + "addTitle": "Adicionar Tag", + "editTitle": "Editar Tag", + "addChildTitle": "Adicionar Tag Filha", + "inputRoot": "Por favor, insira o nome da nova tag de primeiro nível", + "inputEdit": "Por favor, edite o nome da tag", + "inputChild": "Por favor, adicione tag filha para \"{label}\"", + "labelName": "Nome da Tag", + "saving": "Salvando...", + "save": "Salvar", + "deleteConfirm": "Tem certeza de que deseja excluir a tag \"{label}\"?", + "deleteWarning": "Esta ação também excluirá todas as tags filhas e não poderá ser recuperada.", + "emptyLabel": "O nome da tag não pode estar vazio" + }, + "tabs": { + "tree": "Árvore de Domínio", + "structure": "Estrutura de Diretório" + }, + "errors": { + "saveFailed": "Falha ao salvar tag" + }, + "messages": { + "updateSuccess": "Tag atualizada com sucesso" + } + }, + "export": { + "alpacaSettings": "Configurações de Formato Alpaca", + "questionFieldType": "Tipo de Campo de Pergunta", + "useInstruction": "Usar campo instruction", + "useInput": "Usar campo input", + "customInstruction": "Conteúdo personalizado do campo instruction", + "instructionPlaceholder": "Por favor, insira o conteúdo fixo da instrução", + "instructionHelperText": "Quando usar o campo input, pode especificar aqui o conteúdo fixo do instruction", + "title": "Exportar", + "format": "Estilo do Conjunto de Dados", + "fileFormat": "Formato do Arquivo", + "systemPrompt": "Prompt do Sistema", + "systemPromptPlaceholder": "Por favor, insira o prompt do sistema...", + "ReasoninglanguagePlaceholder": "Por favor, insira a linguagem de Raciocínio: Inglês ou Chinês ou outras", + "Reasoninglanguage": "Linguagem de Raciocínio", + "onlyConfirmed": "Exportar apenas dados confirmados", + "example": "Exemplo de Formato", + "confirmExport": "Confirmar Exportação", + "includeCOT": "Incluir Cadeia de Pensamento", + "cotDescription": "Incluir o processo de raciocínio antes da resposta final", + "customFormat": "Formato Personalizado", + "customFormatSettings": "Configurações de Formato Personalizado", + "questionFieldName": "Nome do Campo de Pergunta", + "answerFieldName": "Nome do Campo de Resposta", + "cotFieldName": "Nome do Campo de Cadeia de Pensamento", + "includeLabels": "Incluir Tags", + "includeChunk": "Incluir Bloco de Texto", + "questionOnly": "Exportar apenas perguntas", + "localTab": "Exportar para Local", + "llamaFactoryTab": "Usar no LLaMA Factory", + "huggingFaceTab": "Enviar para Hugging Face", + "configExists": "Arquivo de configuração já existe", + "configPath": "Caminho do arquivo de configuração", + "updateConfig": "Atualizar Configuração do LLaMA Factory", + "noConfig": "Nenhum arquivo de configuração ainda, clique no botão abaixo para gerar", + "generateConfig": "Gerar Configuração do LLaMA Factory", + "huggingFaceComingSoon": "Funcionalidade de exportação para HuggingFace em breve", + "uploadToHuggingFace": "Enviar para HuggingFace", + "datasetName": "Nome do Conjunto de Dados", + "datasetNameHelp": "Formato: nome de usuário/nome do conjunto de dados", + "privateDataset": "Conjunto de Dados Privado", + "datasetSettings": "Configurações do Conjunto de Dados", + "exportOptions": "Opções de Exportação", + "uploadSuccess": "Conjunto de dados enviado com sucesso para HuggingFace", + "viewOnHuggingFace": "Ver no HuggingFace", + "noTokenWarning": "Token do HuggingFace não encontrado. Por favor, configure o token nas configurações do projeto.", + "goToSettings": "Ir para Configurações", + "tokenHelp": "Você pode obter o token na página de configurações do HuggingFace", + "multilingualThinkingFormat": "Pensamento Multilíngue", + "sampleInstruction": "Instrução Humana (obrigatório)", + "sampleOutput": "Resposta do Modelo (obrigatório)", + "sampleSystem": "Prompt do Sistema (opcional)", + "sampleInstruction2": "Segunda Instrução", + "sampleOutput2": "Segunda Resposta", + "sampleSystemShort": "Prompt do Sistema", + "fixedInstruction": "Conteúdo fixo da instrução", + "sampleInput": "Pergunta Humana (obrigatório)", + "sampleInput2": "Segunda Pergunta", + "sampleInputOptional": "Entrada Humana (opcional)", + "sampleUserMessage": "Instrução Humana", + "sampleAssistantMessage": "Resposta do Modelo", + "sampleAnalysis": "Conteúdo da cadeia de pensamento do modelo", + "sampleFinal": "Resposta do Modelo", + "sampleThinking": "Conteúdo da cadeia de pensamento do modelo", + "fetchLabelStatsError": "Falha ao obter estatísticas de tags:" + }, + "datasets": { + "loadingDataset": "Carregando detalhes do conjunto de dados...", + "datasetNotFound": "Conjunto de dados não encontrado", + "optimizeTitle": "Otimização de IA", + "optimizeAdvice": "Sugestão de Otimização", + "optimizePlaceholder": "Por favor, insira suas sugestões de melhoria para a resposta, a IA otimizará a resposta e a cadeia de pensamento de acordo com suas sugestões", + "generatingDataset": "Gerando conjunto de dados", + "aiOptimizeAdvicePlaceholder": "Por favor, insira suas sugestões de melhoria para a resposta, a IA otimizará a resposta e a cadeia de pensamento de acordo com suas sugestões", + "aiOptimizeAdvice": "Por favor, insira suas sugestões de melhoria para a resposta, a IA otimizará a resposta e a cadeia de pensamento de acordo com suas sugestões", + "aiOptimize": "Otimização Inteligente de IA", + "generating": "Gerando conjunto de dados", + "partialSuccess": "Geração de conjunto de dados parcialmente bem-sucedida para perguntas ({{successCount}}/{{total}}), {{failCount}} perguntas falharam", + "generateError": "Falha ao gerar conjunto de dados", + "management": "Conjunto de Dados", + "question": "Pergunta", + "filterAll": "Todos", + "filterConfirmed": "Confirmado", + "filterUnconfirmed": "Não Confirmado", + "createdAt": "Tempo de Criação", + "model": "Modelo Usado", + "domainTag": "Tag de Domínio", + "cot": "Cadeia de Pensamento", + "answer": "Resposta", + "chunkId": "Bloco de Texto", + "confirmed": "Confirmado", + "noTag": "Sem Tag", + "noData": "Nenhum dado", + "rowsPerPage": "Linhas por Página", + "pagination": "{{from}}-{{to}} de {{count}}", + "confirmDeleteMessage": "Tem certeza de que deseja excluir este conjunto de dados? Esta ação não pode ser desfeita.", + "questionLabel": "Pergunta", + "fetchFailed": "Falha ao obter conjunto de dados", + "deleteFailed": "Falha ao excluir conjunto de dados", + "deleteSuccess": "Excluído com sucesso", + "exportSuccess": "Conjunto de dados exportado com sucesso", + "exportFailed": "Falha na exportação", + "exportProgress": "Progresso da Exportação", + "exportingData": "Exportando conjunto de dados", + "processedCount": "Processado {{processed}} / {{total}} itens", + "exportInProgress": "Obtendo dados, por favor, aguarde...", + "exportFinalizing": "Gerando arquivo, quase concluído...", + "loading": "Carregando conjunto de dados...", + "stats": "Total de {{total}} conjuntos de dados, {{confirmed}} confirmados ({{percentage}}%)", + "selected": "{{ count }} conjuntos de dados selecionados no total", + "batchconfirmDeleteMessage": "Tem certeza de que deseja excluir os {{count}} conjuntos de dados selecionados? Esta ação não pode ser desfeita.", + "batchDelete": "Excluir em Lote", + "batchDeleteProgress": "Concluído: {{completed}}/{{total}}", + "batchDeleteCount": "Gerado: {{count}}", + "evaluation": "Anotação do Conjunto de Dados", + "rating": "Avaliação", + "ratingExcellent": "Excelente", + "ratingGood": "Bom", + "ratingAverage": "Médio", + "ratingPoor": "Ruim", + "ratingVeryPoor": "Muito Ruim", + "ratingUnrated": "Não Avaliado", + "customTags": "Tags Personalizadas", + "addCustomTag": "Adicionar tag personalizada...", + "note": "Observação", + "addNote": "Adicionar observação...", + "noNote": "Nenhuma observação", + "clickToAddNote": "Clique para adicionar observação...", + "enterNote": "Por favor, insira a observação...", + "noteShortcuts": "Ctrl+Enter para salvar, Esc para cancelar", + "aiEvaluation": "Avaliação de Qualidade de IA", + "addToEval": "Adicionar ao Conjunto de Dados de Avaliação", + "addToEvalSuccess": "Adicionado ao conjunto de dados de avaliação com sucesso", + "addToEvalFailed": "Falha ao adicionar", + "generateEvalVariant": "Gerar Variante do Conjunto de Avaliação", + "generateVariantFailed": "Falha ao gerar variante", + "saveVariantSuccess": "Salvo no conjunto de dados de avaliação", + "saveVariantFailed": "Falha ao salvar", + "evalVariantTitle": "Gerar Variante do Conjunto de Avaliação", + "evalVariantPreviewTitle": "Confirmar Questões Geradas", + "saveToEval": "Salvar no Conjunto de Avaliação", + "evalVariantConfigHint": "Por favor, selecione o tipo e quantidade de questões a serem geradas, a IA reescreverá com base no par de perguntas e respostas atual.", + "questionType": "Tipo de Questão", + "typeOpenEnded": "Pergunta e Resposta Aberta", + "typeSingleChoice": "Múltipla Escolha", + "typeMultipleChoice": "Múltipla Escolha (Várias Respostas)", + "typeTrueFalse": "Verdadeiro/Falso", + "generateCount": "Quantidade a Gerar", + "evalVariantPreviewHint": "Você pode editar as questões geradas, confirme e salve no conjunto de avaliação após verificação.", + "questionIndex": "Questão {{index}}", + "options": "Opções (Array JSON)", + "optionsHint": "Por exemplo: [\"Opção A\", \"Opção B\"]", + "answerArrayHint": "Para respostas de múltipla escolha, insira um array, como [\"A\", \"C\"]", + "answerBoolHint": "Para questões de verdadeiro/falso, insira ✅ ou ❌", + "generate": "Gerar", + "updateSuccess": "Atualização bem-sucedida", + "updateFailed": "Falha na atualização", + "searchPlaceholder": "Pesquisar conjunto de dados...", + "fieldQuestion": "Pergunta", + "fieldAnswer": "Resposta", + "fieldCOT": "Cadeia de Pensamento", + "fieldLabel": "Tag de Domínio", + "moreFilters": "Mais", + "filtersTitle": "Condições de Filtro", + "filterConfirmationStatus": "Status de Confirmação", + "filterCotStatus": "Status de Cadeia de Pensamento", + "filterHasCot": "Inclui Cadeia de Pensamento", + "filterNoCot": "Não Inclui Cadeia de Pensamento", + "filterScoreRange": "Intervalo de Pontuação", + "filterNoteKeyword": "Palavra-chave da Observação", + "filterNoteKeywordPlaceholder": "Por favor, insira a palavra-chave da observação...", + "filterChunkName": "Nome do Bloco de Texto", + "filterChunkNamePlaceholder": "Por favor, insira o nome do bloco de texto...", + "filterCustomTag": "Tag Personalizada", + "resetFilters": "Redefinir", + "applyFilters": "Aplicar Filtros", + "viewDetails": "Ver Detalhes", + "datasetDetail": "Detalhes do Conjunto de Dados", + "metadata": "Metadados", + "confirmSave": "Confirmar Retenção", + "unconfirm": "Cancelar Confirmação", + "unconfirming": "Cancelando confirmação...", + "uncategorized": "Não Categorizado", + "questionCount": "{{count}} perguntas", + "source": "Fonte", + "generateDataset": "Gerar Conjunto de Dados", + "generateNotImplemented": "Funcionalidade de geração de conjunto de dados não implementada", + "generateSuccess": "Conjunto de dados gerado com sucesso: {{question}}", + "generateFailed": "Falha ao gerar conjunto de dados: {{error}}", + "noTagsAndQuestions": "Nenhuma tag e pergunta disponível", + "answerCount": "{{count}} respostas", + "answered": "Resposta Gerada", + "enableShortcuts": "Atalhos de Paginação", + "shortcutsHelp": "Pressione ← para anterior, → para próximo, y para confirmar, d para excluir", + "filterDistill": "É Conjunto de Dados Destilado", + "filterDistillYes": "Conjunto de Dados Destilado", + "filterDistillNo": "Conjunto de Dados Não Destilado", + "evaluate": "Avaliação de Qualidade", + "evaluating": "Avaliando...", + "batchEvaluate": "Avaliação de Qualidade Automática", + "selectModelFirst": "Por favor, selecione um modelo primeiro", + "evaluateSuccess": "Avaliação concluída! Pontuação: {{score}}/5", + "evaluateFailed": "Falha na avaliação", + "evaluateError": "Falha na avaliação: {{error}}", + "batchEvaluateStarted": "Tarefa de avaliação em lote iniciada, será processada em segundo plano", + "batchEvaluateStartFailed": "Falha ao iniciar avaliação em lote", + "batchEvaluateFailed": "Falha na avaliação em lote: {{error}}", + "scoreRange": "{{min}} - {{max}} pontos", + "singleTurn": "Conjunto de Dados de Pergunta e Resposta de Rodada Única", + "multiTurn": "Conjunto de Dados de Diálogo de Múltiplas Rodadas", + "imageQA": "Conjunto de Dados de Pergunta e Resposta de Imagens", + "conversationDetail": "Detalhes do Diálogo de Múltiplas Rodadas", + "conversationContent": "Conteúdo do Diálogo", + "basicInfo": "Informações Básicas", + "firstQuestion": "Primeira Pergunta", + "conversationScenario": "Cenário do Diálogo", + "conversationRounds": "Número de Rodadas do Diálogo", + "modelUsed": "Modelo Usado", + "qualityScore": "Pontuação de Qualidade", + "notes": "Observações", + "createTime": "Tempo de Criação", + "notSet": "Não Definido", + "noTags": "Sem Tags", + "noNotes": "Sem Observações", + "notEvaluated": "Ainda Não Avaliado", + "round": "Rodada {{round}}", + "system": "Sistema", + "user": "Usuário", + "assistant": "Assistente", + "confirmDelete": "Confirmar Exclusão", + "confirmDeleteConversation": "Tem certeza de que deseja excluir este conjunto de dados de diálogo de múltiplas rodadas? Esta ação não pode ser desfeita.", + "conversationNotFound": "Conjunto de dados de diálogo não existe", + "fetchDataFailed": "Falha ao obter dados", + "saveFailed": "Falha ao salvar", + "saveSuccess": "Salvo com sucesso", + "saving": "Salvando...", + "inputTagsPlaceholder": "Insira tags, separadas por espaço", + "addNotesPlaceholder": "Adicionar informações de observação", + "noConversations": "Nenhum conjunto de dados de diálogo de múltiplas rodadas", + "notRated": "Não Avaliado", + "minScore": "Pontuação Mínima", + "maxScore": "Pontuação Máxima", + "unconfirmed": "Não Confirmado" + }, + "rating": { + "veryPoor": "Muito Ruim", + "poor": "Ruim", + "belowAverage": "Abaixo da Média", + "fair": "Regular", + "average": "Médio", + "good": "Bom", + "veryGood": "Muito Bom", + "excellent": "Excelente", + "outstanding": "Excepcional", + "perfect": "Perfeito", + "unrated": "Não Avaliado" + }, + "tags": { + "noTags": "Nenhuma tag", + "addTag": "Adicionar tag...", + "addCustomTag": "Adicionar tag personalizada", + "maxTagsReached": "Máximo de {{maxTags}} tags permitidas", + "availableTagsHint": "Pode selecionar de tags existentes ou inserir novas tags" + }, + "import": { + "title": "Importar", + "fileUpload": "Upload de Arquivo", + "fileUploadDescription": "Fazer upload de arquivo local para importar conjunto de dados", + "uploadFile": "Enviar Arquivo", + "supportedFormats": "Suporta arquivos nos formatos JSON, JSONL, CSV", + "dragDropFile": "Arraste o arquivo para cá ou clique para selecionar o arquivo", + "dropFileHere": "Solte para fazer upload do arquivo", + "maxFileSize": "Tamanho máximo do arquivo: 50MB", + "processingFile": "Processando arquivo...", + "uploadedFiles": "Arquivos Enviados", + "uploadError": "Falha no upload do arquivo, por favor, verifique se o formato do arquivo está correto", + "mapFields": "Mapeamento de Campos", + "importing": "Importando", + "fieldMapping": "Mapeamento de Campos", + "mappingDescription": "Por favor, mapeie os campos dos dados de origem para os campos de destino. O sistema já identificou automaticamente possíveis relações de mapeamento, você pode ajustar conforme necessário.", + "selectMapping": "Selecionar Mapeamento de Campos", + "questionField": "Campo de Pergunta", + "answerField": "Campo de Resposta", + "cotField": "Campo de Cadeia de Pensamento", + "tagsField": "Campo de Tags", + "selectField": "Selecionar Campo", + "questionDesc": "Pergunta do usuário ou conteúdo de entrada (obrigatório)", + "answerDesc": "Resposta da IA ou conteúdo de saída (obrigatório)", + "cotDesc": "Cadeia de pensamento ou processo de raciocínio (opcional)", + "tagsDesc": "Array de tags, múltiplas tags separadas por vírgula (opcional)", + "dataPreview": "Pré-visualização de Dados", + "previewNote": "Exibindo os primeiros 3 registros, cada valor de campo exibindo no máximo 100 caracteres", + "confirmMapping": "Confirmar Mapeamento", + "requiredFields": "Por favor, selecione pelo menos o mapeamento dos campos de pergunta e resposta", + "mappingRequired": "Campos de pergunta e resposta são obrigatórios", + "duplicateMapping": "Não é possível mapear múltiplos campos de destino para o mesmo campo de origem", + "noPreviewData": "Nenhum dado disponível para pré-visualização", + "preparingData": "Preparando dados...", + "uploadingData": "Enviando dados...", + "processing": "Processando... {{processed}}/{{total}}", + "completed": "Importação Concluída", + "importStats": "Estatísticas de Importação", + "total": "Total: {{count}}", + "success": "Sucesso: {{count}}", + "failed": "Falha: {{count}}", + "source": "Fonte de Dados", + "description": "Descrição", + "errors": "Mensagens de Erro", + "moreErrors": "Mais {{count}} erros não exibidos...", + "importSuccess": "Importação do conjunto de dados concluída!", + "enterDatasetName": "Por favor, insira o nome do conjunto de dados", + "noDatasetFound": "Nenhum conjunto de dados correspondente encontrado", + "complete": "Concluir", + "addToEval": "Adicionar ao Conjunto de Dados de Avaliação", + "addToEvalSuccess": "Adicionado ao conjunto de dados de avaliação com sucesso", + "addToEvalFailed": "Falha ao adicionar", + "generateEvalVariant": "Gerar Variante do Conjunto de Avaliação", + "selectModelFirst": "Por favor, selecione um modelo primeiro", + "generateVariantFailed": "Falha ao gerar variante", + "saveVariantSuccess": "Salvo no conjunto de dados de avaliação", + "saveVariantFailed": "Falha ao salvar", + "evalVariantTitle": "Gerar Variante do Conjunto de Avaliação", + "evalVariantHint": "A IA gerou novas variantes de teste com base no par de perguntas e respostas original, você pode editar manualmente antes de salvar.", + "saveToEval": "Salvar no Conjunto de Avaliação", + "evalVariantConfigHint": "Por favor, selecione o tipo e quantidade de questões a serem geradas, a IA reescreverá com base no par de perguntas e respostas atual.", + "questionType": "Tipo de Questão", + "typeOpenEnded": "Pergunta e Resposta Aberta", + "typeSingleChoice": "Múltipla Escolha", + "typeMultipleChoice": "Múltipla Escolha (Várias Respostas)", + "typeTrueFalse": "Verdadeiro/Falso", + "typeShortAnswer": "Resposta Curta", + "generateCount": "Quantidade a Gerar", + "evalVariantPreviewHint": "Você pode editar as questões geradas, confirme e salve no conjunto de avaliação após verificação.", + "questionIndex": "Questão {{index}}", + "options": "Opções (Array JSON)", + "optionsHint": "Por exemplo: [\"Opção A\", \"Opção B\"]", + "answerArrayHint": "Para respostas de múltipla escolha, insira um array, como [\"A\", \"C\"]", + "answerBoolHint": "Para questões de verdadeiro/falso, insira ✅ ou ❌", + "evalVariantPreviewTitle": "Confirmar Questões Geradas", + "generate": "Gerar" + }, + "update": { + "newVersion": "Nova Versão", + "newVersionAvailable": "Nova versão disponível", + "currentVersion": "Versão Atual", + "latestVersion": "Última Versão", + "downloadNow": "Baixar Agora", + "downloading": "Baixando:", + "installNow": "Instalar Agora", + "updating": "Atualizando...", + "updateNow": "Atualizar Agora", + "viewRelease": "Baixar Última Versão", + "checking": "Verificando atualizações...", + "noUpdates": "Já está na versão mais recente", + "updateError": "Erro na atualização", + "updateSuccess": "Atualização bem-sucedida", + "restartRequired": "Necessário reiniciar o aplicativo", + "restartNow": "Reiniciar Agora", + "restartLater": "Reiniciar Mais Tarde" + }, + "datasetSquare": { + "title": "Praça de Conjuntos de Dados", + "subtitle": "Descubra e explore vários recursos de conjuntos de dados públicos para auxiliar no treinamento e pesquisa do seu modelo", + "searchPlaceholder": "Pesquisar palavras-chave do conjunto de dados...", + "searchVia": "Via", + "categoryTitle": "Categorias de Conjuntos de Dados", + "categories": { + "all": "Todos", + "popular": "Recomendações Populares", + "chinese": "Recursos em Chinês", + "english": "Recursos em Inglês", + "research": "Dados de Pesquisa", + "multimodal": "Multimodal" + }, + "foundResources": "{{count}} recursos de conjuntos de dados encontrados", + "currentFilter": "Filtro atual: {{category}}", + "noDatasets": "Nenhum conjunto de dados correspondente encontrado", + "tryOtherCategories": "Por favor, tente outras categorias ou volte para ver todos os conjuntos de dados", + "dataset": "Conjunto de Dados", + "viewDataset": "Ver Conjunto de Dados" + }, + "playground": { + "title": "Teste de Modelo", + "selectModelFirst": "Por favor, selecione pelo menos um modelo", + "sendFirstMessage": "Envie a primeira mensagem para iniciar o teste", + "inputMessage": "Digite a mensagem...", + "send": "Enviar", + "outputMode": "Modo de Saída", + "normalOutput": "Saída Normal", + "streamingOutput": "Saída em Streaming", + "clearConversation": "Limpar Diálogo", + "selectModelMax3": "Selecionar Modelo (máximo 3)", + "reasoningProcess": "Processo de Raciocínio" + }, + "chunks": { + "title": "Blocos de Texto", + "defaultTitle": "Título Padrão" + }, + "documentation": "Documentação", + "models": { + "configNotFound": "Configuração do modelo não encontrada", + "parseError": "Falha ao analisar configuração do modelo", + "fetchFailed": "Falha ao obter modelo", + "saveFailed": "Falha ao salvar configuração do modelo", + "pleaseSelectModel": "Por favor, selecione pelo menos um modelo", + "title": "Gerenciamento de Modelos", + "add": "Adicionar Modelo", + "unselectedModel": "Modelo Não Selecionado", + "unconfiguredAPIKey": "API Key Não Configurada", + "saveAllModels": "Salvar Todas as Configurações de Modelos", + "edit": "Editar", + "delete": "Excluir", + "modelId": "ID do Modelo", + "modelName": "Nome do Modelo", + "modelNamePlaceholder": "Por favor, insira o nome do modelo (opcional, padrão é o ID do modelo)", + "modelIdPlaceholder": "Nome do modelo (pode inserir personalizado)", + "endpoint": "Endereço da Interface", + "apiKey": "Chave da API", + "provider": "Provedor (pode inserir personalizado)", + "localModel": "Modelo Local", + "apiKeyConfigured": "API Key já configurada", + "apiKeyNotConfigured": "API Key não configurada", + "temperature": "Temperatura do Modelo", + "maxTokens": "Número Máximo de Tokens Gerados", + "maxTokensInputTip": "Intervalo do controle deslizante: 1-{{max}}. Você também pode inserir qualquer número inteiro positivo.", + "topP": "Top P", + "type": "Tag do Modelo", + "text": "Modelo de Linguagem", + "vision": "Modelo de Visão", + "typeTips": "Se deseja usar modelo de visão personalizado para analisar PDF, certifique-se de configurar pelo menos um modelo grande de visão", + "refresh": "Atualizar Lista de Modelos", + "configuredModels": "Modelos Configurados", + "unconfiguredModels": "Modelos Não Configurados", + "noConfiguredModels": "Nenhum modelo configurado ainda", + "noUnconfiguredModels": "Nenhum modelo não configurado ainda", + "checkEndpointHealth": "Verificar Saúde do Endpoint", + "checkAllEndpointHealth": "Verificar Todos os Endpoints com um Clique", + "endpointHealthy": "Endpoint Saudável", + "endpointCheckFailed": "Falha na verificação do endpoint", + "endpointMissing": "Endpoint está vazio", + "endpointReachableModelMissing": "Endpoint acessível, mas o modelo atual não está na lista de retorno", + "healthCheckSummary": "Verificação de saúde concluída: {{okCount}} normal, {{failCount}} falha", + "checking": "Verificando...", + "healthy": "Saudável", + "reachable": "Acessível", + "unhealthy": "Anormal", + "notChecked": "Não Verificado" + }, + "stats": { + "ongoingProjects": "Projetos em Execução", + "questionCount": "Quantidade de Perguntas", + "generatedDatasets": "Conjuntos de Dados Gerados", + "supportedModels": "Modelos Suportados" + }, + "migration": { + "title": "【Importante】Migração de Dados Históricos", + "description": "Para melhorar o desempenho de recuperação de grandes conjuntos de dados, a partir da versão 1.3.1, o Easy Dataset mudou o método de armazenamento de arquivos para armazenamento em banco de dados local. Detectamos que você tem projetos históricos que ainda não foram migrados. Antes de concluir a migração, você não poderá acessar esses projetos. Por favor, conclua a migração o mais rápido possível!", + "projectsList": "Projetos Não Migrados", + "migrate": "Iniciar Migração", + "migrating": "Migrando...", + "success": "{{count}} projeto(s) migrado(s) com sucesso", + "failed": "Falha na migração", + "checkFailed": "Falha ao verificar projetos não migrados", + "checkError": "Erro ao verificar projetos não migrados", + "starting": "Iniciando tarefa de migração...", + "processing": "Processando tarefa de migração...", + "completed": "Migração concluída", + "startFailed": "Falha ao iniciar tarefa de migração", + "statusFailed": "Falha ao obter status da migração", + "taskNotFound": "Tarefa de migração não existe", + "progressStatus": "{{completed}}/{{total}} projeto(s) migrado(s)", + "openDirectory": "Abrir Diretório do Projeto", + "deleteDirectory": "Excluir Diretório do Projeto", + "confirmDelete": "Tem certeza de que deseja excluir este diretório de projeto? Esta ação não pode ser desfeita.", + "openDirectoryFailed": "Falha ao abrir diretório do projeto", + "deleteDirectoryFailed": "Falha ao excluir diretório do projeto" + }, + "distill": { + "title": "Destilação de Dados", + "generateRootTags": "Gerar Tags de Primeiro Nível", + "generateSubTags": "Gerar Tags Filhas", + "generateQuestions": "Gerar Perguntas", + "generateRootTagsTitle": "Gerar Tags de Domínio de Primeiro Nível", + "generateSubTagsTitle": "Gerar Tags Filhas para {{parentTag}}", + "generateQuestionsTitle": "Gerar Perguntas para {{tag}}", + "parentTag": "Tag Pai", + "parentTagPlaceholder": "Por favor, insira o nome da tag pai (ex: Esportes, Tecnologia, etc.)", + "parentTagHelp": "Insira um tema de domínio, o sistema gerará tags relacionadas com base nisso", + "tagCount": "Quantidade de Tags", + "tagCountHelp": "Insira a quantidade de tags a serem geradas, máximo de 100", + "questionCount": "Quantidade de Perguntas", + "questionCountHelp": "Insira a quantidade de perguntas a serem geradas, máximo de 100", + "generatedTags": "Tags Geradas", + "generatedQuestions": "Perguntas Geradas", + "generateTags": "Gerar Tags", + "tagPath": "Caminho da Tag", + "noTags": "Nenhuma tag ainda", + "noQuestions": "Nenhuma pergunta ainda", + "clickGenerateButton": "Clique no botão de gerar acima para começar a criar tags", + "selectModelFirst": "Por favor, selecione um modelo primeiro", + "selectModel": "Selecionar Modelo", + "generateTagsError": "Falha ao gerar tags", + "generateQuestionsError": "Falha ao gerar perguntas", + "deleteTagConfirmTitle": "Confirmar exclusão da tag? Isso excluirá todas as tags filhas, perguntas e conjuntos de dados associados a esta tag", + "editTagTitle": "Editar Tag", + "tagName": "Nome da Tag", + "labelRequired": "O nome da tag não pode estar vazio", + "tagUpdateSuccess": "Tag atualizada com sucesso", + "tagUpdateFailed": "Falha ao atualizar tag", + "unknownTag": "Tag desconhecida", + "autoDistillButton": "Destilação Automática Completa de Conjunto de Dados", + "autoDistillTitle": "Configuração de Destilação Automática Completa de Conjunto de Dados", + "distillTopic": "Tema da Destilação", + "tagLevels": "Níveis de Tags", + "tagLevelsHelper": "Definir a quantidade de níveis, máximo de {{max}} níveis", + "tagsPerLevel": "Quantidade de Tags por Nível", + "tagsPerLevelHelper": "Quantidade de tags filhas geradas sob cada tag pai, máximo de {{max}}", + "questionsPerTag": "Quantidade de Perguntas por Tag", + "questionsPerTagHelper": "Quantidade de perguntas geradas para cada tag folha, máximo de {{max}}", + "estimationInfo": "Informações Estimadas da Tarefa", + "estimatedTags": "Quantidade Estimada de Tags a Serem Geradas", + "estimatedQuestions": "Quantidade Estimada de Perguntas a Serem Geradas", + "currentTags": "Quantidade Atual de Tags", + "currentQuestions": "Quantidade Atual de Perguntas", + "newTags": "Quantidade Estimada de Novas Tags", + "newQuestions": "Quantidade Estimada de Novas Perguntas", + "startAutoDistill": "Iniciar Destilação Automática", + "autoDistillProgress": "Progresso da Destilação Automática", + "overallProgress": "Progresso Geral", + "tagsProgress": "Progresso da Construção de Tags", + "questionsProgress": "Progresso da Geração de Perguntas", + "currentStage": "Estágio Atual", + "realTimeLogs": "Logs em Tempo Real", + "waitingForLogs": "Aguardando saída de logs...", + "autoDistillStarted": "{{time}} Tarefa de destilação automática iniciada", + "autoDistillInsufficientError": "A configuração atual não produzirá novas tags ou perguntas, por favor, ajuste os parâmetros", + "stageInitializing": "Inicializando...", + "stageBuildingLevel1": "Construindo tags do primeiro nível", + "stageBuildingLevel2": "Construindo tags do segundo nível", + "stageBuildingLevel3": "Construindo tags do terceiro nível", + "stageBuildingLevel4": "Construindo tags do quarto nível", + "stageBuildingLevel5": "Construindo tags do quinto nível", + "stageBuildingQuestions": "Gerando perguntas", + "stageBuildingDatasets": "Gerando conjuntos de dados", + "stageCompleted": "Tarefa concluída", + "datasetsProgress": "Progresso da Geração de Conjuntos de Dados", + "rootTopicHelperText": "Por padrão, usa o nome do projeto como tema de destilação de primeiro nível. Se precisar alterar, vá para as configurações do projeto para alterar o nome do projeto.", + "addChildTag": "Gerar Tag Filha", + "datasetType": "Tipo de Conjunto de Dados", + "singleTurnDataset": "Conjunto de Dados de Diálogo de Rodada Única", + "multiTurnDataset": "Conjunto de Dados de Diálogo de Múltiplas Rodadas", + "bothDatasetTypes": "Gerar ambos os tipos de conjuntos de dados", + "autoDistillTaskDetail": "Tarefa de destilação automática: {{topic}}", + "backgroundTaskCreated": "Tarefa de destilação em segundo plano criada, pode verificar o progresso no gerenciamento de tarefas", + "backgroundTaskFailed": "Falha ao criar tarefa em segundo plano", + "taskExecutionError": "Erro na execução da tarefa: {{error}}" + }, + "tasks": { + "pending": "{{count}} tarefa(s) em processamento", + "completed": "Todas as tarefas concluídas", + "title": "Centro de Gerenciamento de Tarefas", + "loading": "Carregando lista de tarefas...", + "empty": "Nenhum registro de tarefa ainda", + "confirmDelete": "Confirmar exclusão desta tarefa?", + "confirmAbort": "Confirmar interrupção desta tarefa? A tarefa será interrompida.", + "deleteSuccess": "Tarefa excluída", + "deleteFailed": "Falha ao excluir tarefa", + "abortSuccess": "Tarefa interrompida", + "abortFailed": "Falha ao interromper tarefa", + "status": { + "processing": "Processando", + "completed": "Concluído", + "failed": "Falhou", + "aborted": "Interrompido", + "unknown": "Desconhecido" + }, + "types": { + "text-processing": "Processamento de Texto", + "file-processing": "Processamento de Arquivo", + "data-cleaning": "Limpeza de Dados", + "question-generation": "Geração de Perguntas", + "answer-generation": "Geração de Respostas", + "multi-turn-generation": "Geração de Diálogo de Múltiplas Rodadas", + "eval-generation": "Geração de Conjunto de Avaliação", + "image-question-generation": "Geração de Perguntas de Imagem", + "data-distillation": "Destilação de Dados", + "pdf-processing": "Análise de PDF" + }, + "filters": { + "status": "Status da Tarefa", + "type": "Tipo de Tarefa" + }, + "actions": { + "refresh": "Atualizar Lista de Tarefas", + "delete": "Excluir Tarefa", + "abort": "Interromper Tarefa" + }, + "table": { + "type": "Tipo de Tarefa", + "status": "Status", + "progress": "Progresso", + "note": "Observação", + "createTime": "Tempo de Criação", + "endTime": "Tempo de Conclusão", + "duration": "Tempo de Execução", + "model": "Modelo Usado", + "detail": "Detalhes da Tarefa", + "actions": "Ações" + }, + "duration": { + "seconds": "{{seconds}} segundos", + "minutes": "{{minutes}} minutos {{seconds}} segundos", + "hours": "{{hours}} horas {{minutes}} minutos" + }, + "fetchFailed": "Falha ao obter lista de tarefas", + "createSuccess": "Tarefa criada com sucesso", + "createFailed": "Falha ao criar tarefa", + "multiTurnCreateSuccess": "Tarefa de conjunto de dados de diálogo de múltiplas rodadas criada com sucesso", + "notes": { + "selectedChunks": "{{count}} blocos de texto selecionados", + "fileBatch": "Parâmetros de processamento de arquivo: {{count}} arquivo(s) (estratégia: {{strategy}})", + "jsonParams": "Parâmetros da tarefa configurados", + "noChunksQuestion": "Nenhum bloco de texto precisa gerar perguntas", + "noChunksCleaning": "Nenhum bloco de texto precisa ser limpo", + "processingFailed": "Falha na tarefa: {{error}}", + "questionSummary": "Processado {{processed}}/{{total}}, sucesso {{succeeded}}, falha {{failed}}, perguntas geradas {{generated}}", + "datasetSummary": "Processado {{processed}}/{{total}}, sucesso {{succeeded}}, falha {{failed}}, conjuntos de dados gerados {{generated}}", + "cleaningSummary": "Processado {{processed}}/{{total}}, sucesso {{succeeded}}, falha {{failed}}, comprimento original {{original}}, após limpeza {{cleaned}}", + "genericSummary": "Processado {{processed}}/{{total}}, sucesso {{succeeded}}, falha {{failed}}" + } + }, + "gaPairs": { + "title": "Gerenciamento de Pares Gênero-Público", + "loading": "Carregando pares gênero-público...", + "addPair": "Adicionar Par Gênero-Público", + "saveChanges": "Salvar Alterações", + "saving": "Salvando...", + "restoreBackup": "Restaurar Backup", + "noGaPairsTitle": "Nenhum Par Gênero-Público Encontrado", + "noGaPairsDescription": "Gerar pares gênero-público impulsionados por IA para este arquivo", + "generateGaPairs": "Gerar Pares Gênero-Público", + "generating": "Gerando...", + "generateMore": "Gerar Mais Pares Gênero-Público", + "activePairs": "Pares Gênero-Público Ativos ({{active}}/{{total}})", + "pairNumber": "Par Gênero-Público #{{number}}", + "active": "Ativo", + "deleteTooltip": "Excluir Par Gênero-Público", + "genre": "Gênero", + "genreDescription": "Descrição do Gênero", + "audience": "Público", + "audienceDescription": "Descrição do Público", + "addDialogTitle": "Adicionar Novo Par Gênero-Público", + "genreTitle": "Título do Gênero", + "audienceTitle": "Título do Público", + "genreTitlePlaceholder": "Por favor, insira o título do gênero...", + "genreDescPlaceholder": "Descreva detalhadamente este gênero...", + "audienceTitlePlaceholder": "Por favor, insira o título do público...", + "audienceDescPlaceholder": "Descreva detalhadamente o público-alvo...", + "cancel": "Cancelar", + "addPairButton": "Adicionar Par Gênero-Público", + "requiredFields": "Título do gênero e título do público são obrigatórios", + "restoredFromBackup": "Restaurado do backup", + "allPairsDeleted": "Todos os pares gênero-público excluídos com sucesso", + "pairsSaved": "{{count}} pares gênero-público salvos com sucesso", + "additionalPairsGenerated": "{{count}} pares gênero-público adicionais gerados com sucesso. Total: {{total}}", + "validationError": "Par Gênero-Público {{number}}: Título do gênero e título do público são obrigatórios", + "loadError": "Não foi possível carregar pares gênero-público: {{error}}", + "generateError": "Falha ao gerar pares gênero-público", + "saveError": "Falha ao salvar pares gênero-público", + "noActiveModel": "Por favor, configure o modelo de IA nas configurações antes de gerar pares gênero-público.", + "contentTooShort": "O conteúdo do arquivo é muito curto ou inadequado para gerar pares gênero-público.", + "configError": "Erro na configuração do modelo de IA. Podem faltar dependências necessárias.", + "serverError": "Erro do servidor ({{status}}). Por favor, tente novamente mais tarde.", + "emptyResponse": "O serviço de geração retornou resposta vazia", + "generationFailed": "Falha na geração", + "saveOperationFailed": "Falha na operação de salvamento", + "serviceNotAvailable": "Serviço de geração de pares gênero-público não disponível. Por favor, verifique sua configuração de API.", + "requestFailed": "Falha na solicitação ({{status}}). Por favor, tente novamente.", + "internalServerError": "Ocorreu um erro interno do servidor.", + "batchGenerate": "Gerar Pares Gênero-Público em Lote", + "batchGenerateDescription": "Gerará pares gênero-público em lote para {{count}} arquivo(s) selecionado(s), esta operação pode levar algum tempo.", + "appendMode": "Modo de Anexação", + "appendModeDescription": "Gerar mais pares gênero-público para arquivos que já possuem pares gênero-público, em vez de substituir", + "selectAtLeastOneFile": "Por favor, selecione pelo menos um arquivo", + "noDefaultModel": "Nenhum modelo padrão definido, por favor, configure o modelo nas configurações do projeto primeiro", + "incompleteModelConfig": "Configuração do modelo incompleta, por favor, verifique as configurações do modelo", + "missingApiKey": "API Key não configurada para o modelo, por favor, adicione a API Key nas configurações do modelo", + "loadingProjectModel": "Carregando modelo do projeto...", + "usingModel": "Usando modelo", + "startGeneration": "Iniciar Geração", + "batchGenCompleted": "Geração em lote concluída! Pares gênero-público gerados com sucesso para {{success}}/{{total}} arquivo(s).", + "generationError": "Erro durante o processo de geração: {{error}}", + "fetchProjectInfoFailed": "Falha ao obter informações do projeto: {{status}}", + "fetchModelConfigFailed": "Falha ao obter configuração do modelo: {{status}}", + "fetchProjectModelError": "Erro ao obter configuração do modelo do projeto", + "batchGenerationFailed": "Falha na geração em lote de pares gênero-público", + "batchGenerationSuccess": "Pares gênero-público gerados com sucesso para {{count}} arquivo(s)", + "selectAllFiles": "Selecionar Todos", + "deselectAllFiles": "Desmarcar Todos", + "batchGenerateTitle": "Gerar Pares Gênero-Público em Lote", + "generationMode": "Modo de Geração", + "aiGenerateMode": "Geração por IA", + "manualAddMode": "Adição Manual", + "genreDesc": "Descrição do Gênero", + "audienceDesc": "Descrição do Público", + "manualGaPairRequired": "Por favor, preencha o título do gênero e o título do público", + "batchAddManual": "Adicionar em Lote" + }, + "batchEdit": { + "title": "Edição em Lote de Blocos de Texto", + "batchEdit": "Edição em Lote", + "batchEditTooltip": "Editar em lote os blocos de texto selecionados", + "position": "Posição de Adição", + "atBeginning": "Adicionar no Início", + "atEnd": "Adicionar no Final", + "contentToAdd": "Conteúdo a Adicionar", + "contentPlaceholder": "Por favor, insira o conteúdo a ser adicionado ao bloco de texto...", + "contentRequired": "Por favor, insira o conteúdo a ser adicionado", + "contentHelp": "Este conteúdo será adicionado a todos os blocos de texto selecionados", + "preview": "Efeito de Pré-visualização", + "allChunksSelected": "Todos os {{count}} blocos de texto selecionados", + "selectedChunks": "{{selected}} / {{total}} blocos de texto selecionados", + "processing": "Processando...", + "applyToChunks": "Aplicar a {{count}} blocos de texto", + "editSuccess": "{{count}} blocos de texto editados com sucesso", + "editFailed": "Falha na edição em lote", + "previewNote": "Acima está o efeito de pré-visualização do primeiro bloco de texto selecionado, todos os blocos de texto selecionados serão modificados da mesma forma" + }, + "errors": { + "projectIdRequired": "ID do projeto não pode estar vazio", + "getDatasetsFailed": "Falha ao obter conjuntos de dados", + "getTagStatsFailed": "Falha ao obter estatísticas de tags", + "deleteFileFailed": "Erro ao excluir arquivo", + "recordNotFound": "Registro atual não existe", + "mineruTokenNotFound": "Configuração de token não encontrada, por favor, verifique se o token MinerU está configurado nas configurações de tarefa", + "mineruLocalUrlNotFound": "Configuração de URL local do MinerU não encontrada, por favor, verifique se a URL local do MinerU está configurada nas configurações de tarefa" + }, + "sampleData": { + "questionContent": "Conteúdo da Pergunta", + "answerContent": "Conteúdo da Resposta", + "cotContent": "Conteúdo do Processo de Cadeia de Pensamento", + "domainLabel": "Tag de Domínio", + "textChunk": "Bloco de Texto" + }, + "exportDialog": { + "balancedExport": "Exportação Balanceada", + "balancedExportTitle": "Configurações de Exportação Balanceada", + "balancedExportDescription": "Configure a quantidade de dados para cada categoria de acordo com as tags de domínio, realizando exportação balanceada do conjunto de dados", + "quickSettings": "Configurações Rápidas", + "setAllTo50": "Definir Todos para 50", + "setAllTo100": "Definir Todos para 100", + "setAllTo200": "Definir Todos para 200", + "customAmount": "Quantidade Personalizada", + "tagName": "Nome da Tag", + "availableCount": "Quantidade Disponível", + "exportCount": "Quantidade a Exportar", + "settings": "Configurações", + "totalExportCount": "Quantidade Total de Exportação", + "tagCount": "Quantidade de Tags", + "export": "Exportar" + }, + "imageDatasets": { + "title": "Conjunto de Dados de Pergunta e Resposta de Imagens", + "subtitle": "Gerencie e otimize seu conjunto de dados de pergunta e resposta de imagens", + "description": "Gerencie e otimize seu conjunto de dados de pergunta e resposta de imagens.", + "searchPlaceholder": "Pesquisar perguntas ou respostas...", + "noAnswer": "Nenhuma resposta ainda", + "labels": "Tags", + "typeLabel": "Tag", + "typeCustom": "JSON Personalizado", + "typeText": "Texto Normal", + "unscored": "Não pontuado", + "confirmed": "Confirmado", + "unconfirmed": "Não confirmado", + "view": "Ver Detalhes", + "evaluate": "Avaliação de Qualidade", + "delete": "Excluir", + "deleteConfirm": "Tem certeza de que deseja excluir este conjunto de dados?", + "imageName": "Nome da Imagem", + "status": "Status", + "scoreRange": "Intervalo de Pontuação", + "noData": "Nenhum conjunto de dados de imagem", + "noDataTip": "Por favor, gere primeiro o conjunto de dados de pergunta e resposta no gerenciamento de imagens", + "fetchFailed": "Falha ao obter conjunto de dados", + "fetchDetailFailed": "Falha ao obter detalhes", + "deleteSuccess": "Excluído com sucesso", + "deleteFailed": "Falha ao excluir", + "updateSuccess": "Atualização bem-sucedida", + "updateFailed": "Falha na atualização", + "regenerateSuccess": "Reconhecimento de IA bem-sucedido", + "regenerateFailed": "Falha no reconhecimento de IA", + "notFound": "Conjunto de dados não existe", + "detail": "Detalhes", + "image": "Imagem", + "question": "Pergunta", + "answer": "Resposta", + "selectLabels": "Selecionar Tags", + "noLabels": "Nenhuma tag selecionada", + "jsonPlaceholder": "Insira dados no formato JSON...", + "metadata": "Metadados", + "score": "Pontuação", + "tags": "Tags", + "addTag": "Adicionar tag...", + "note": "Observação", + "notePlaceholder": "Adicionar informações de observação...", + "modelInfo": "Informações do Modelo", + "createdAt": "Tempo de Criação", + "updatedAt": "Tempo de Atualização", + "exportTitle": "Exportar Conjunto de Dados de Imagens", + "exportFormat": "Formato de Exportação", + "rawFormat": "Formato Bruto", + "customFormat": "Formato Personalizado", + "exportImagesOption": "Exportar Arquivos de Imagem", + "exportImagesDesc": "Empacotar todas as imagens em um arquivo ZIP para download, após o download, pode descompactar manualmente na pasta Images no mesmo diretório do arquivo de conjunto de dados", + "includeImagePath": "Incluir caminho da imagem no conjunto de dados", + "includeImagePathDesc": "Adicionar caminho da imagem na pergunta ou resposta (formato: /images/nome_da_imagem)", + "systemPrompt": "Prompt do Sistema (opcional)", + "systemPromptPlaceholder": "Insira o prompt do sistema...", + "confirmedOnly": "Exportar apenas conjuntos de dados confirmados", + "exportTip": "Respostas em formato de tag serão automaticamente analisadas como texto (separadas por vírgula)", + "exportSuccess": "Conjunto de dados exportado com sucesso", + "exportFailed": "Falha na exportação", + "noDataToExport": "Nenhum dado para exportar", + "exportImagesSuccess": "Pacote de imagens exportado com sucesso", + "exportImagesFailed": "Falha na exportação de imagens" + }, + "images": { + "resolution": "Resolução", + "uploadTime": "Tempo de Envio", + "fileName": "Nome do Arquivo", + "title": "Gerenciamento de Imagens", + "importImages": "Importar Imagens", + "searchPlaceholder": "Pesquisar nome da imagem...", + "hasQuestions": "Status de Perguntas", + "hasDatasets": "Status de Conjuntos de Dados", + "withQuestions": "Perguntas Geradas", + "withoutQuestions": "Perguntas Não Geradas", + "withDatasets": "Conjuntos de Dados Gerados", + "withoutDatasets": "Conjuntos de Dados Não Gerados", + "noImages": "Nenhuma imagem", + "noImagesDescription": "Comece a importar imagens, crie seu primeiro conjunto de dados de imagens", + "preview": "Pré-visualização", + "questions": "Perguntas", + "datasets": "Conjuntos de Dados", + "datasetCount": "Quantidade de Conjuntos de Dados", + "generateQuestions": "Gerar Perguntas", + "generateDataset": "Gerar Conjunto de Dados", + "deleteConfirm": "Tem certeza de que deseja excluir esta imagem?", + "deleteSuccess": "Excluído com sucesso", + "deleteFailed": "Falha ao excluir", + "batchDelete": "Excluir em Lote", + "selectImagesToDelete": "Por favor, selecione imagens para excluir", + "batchDeleteConfirm": "Tem certeza de que deseja excluir as {{count}} imagens selecionadas?", + "batchDeleteSuccess": "{{count}} imagem(ns) excluída(s) com sucesso", + "batchDeletePartialSuccess": "{{success}} excluída(s) com sucesso, {{fail}} falha(s)", + "batchDeleteFailed": "Falha na exclusão em lote", + "importTip": "Selecione um ou mais diretórios contendo imagens, todas as imagens serão importadas para o projeto (imagens com o mesmo nome serão substituídas)", + "selectDirectory": "Selecionar Diretório", + "directoryPath": "Caminho do Diretório", + "enterDirectoryPath": "Por exemplo: /Users/username/Pictures", + "selectedDirectories": "Diretórios Selecionados", + "selectAtLeastOne": "Por favor, selecione pelo menos um diretório", + "importSuccess": "{{count}} imagem(ns) importada(s) com sucesso", + "importFailed": "Falha na importação", + "startImport": "Iniciar Importação", + "addDirectory": "Adicionar Diretório", + "importFromDirectory": "Importar do Diretório", + "importFromPdf": "Importar do PDF", + "importFromZip": "Importar do ZIP", + "pdfImportTip": "Selecione o arquivo PDF, o sistema converterá automaticamente para imagens e importará", + "zipImportTip": "Selecione o arquivo ZIP, o sistema descompactará automaticamente e importará as imagens", + "clickToSelectPdf": "Clique para selecionar arquivo PDF", + "clickToSelectZip": "Clique para selecionar arquivo ZIP", + "supportedFormat": "Formato suportado: PDF", + "supportedZipFormat": "Formato suportado: ZIP", + "fileSize": "Tamanho do Arquivo", + "selectedFile": "Arquivo Selecionado", + "invalidPdfFile": "Por favor, selecione um arquivo PDF válido", + "invalidZipFile": "Por favor, selecione um arquivo ZIP válido", + "selectPdfFile": "Por favor, selecione o arquivo PDF", + "selectZipFile": "Por favor, selecione o arquivo ZIP", + "pdfImportSuccess": "{{count}} imagem(ns) importada(s) com sucesso do PDF \"{{name}}\"", + "pdfImportFailed": "Falha na importação do PDF", + "zipImportSuccess": "{{count}} imagem(ns) importada(s) com sucesso do pacote \"{{name}}\"", + "zipImportFailed": "Falha na importação do pacote", + "convertAndImport": "Converter e Importar", + "extractAndImport": "Descompactar e Importar", + "electronRequired": "Esta funcionalidade precisa ser usada no aplicativo desktop", + "selectDirectoryFailed": "Falha ao selecionar diretório", + "imageName": "Nome da Imagem", + "questionCount": "Quantidade de Perguntas", + "questionCountHelp": "Gerar 1-10 perguntas", + "size": "Tamanho", + "dimensions": "Dimensões", + "currentModel": "Modelo Atual", + "selectModelFirst": "Por favor, selecione um modelo primeiro", + "visionModelRequired": "Por favor, selecione um modelo com suporte a visão (como GPT-4 Vision, Claude, etc.)", + "countRange": "A quantidade de perguntas deve estar entre 1-10", + "questionsGenerated": "{{count}} pergunta(s) gerada(s) com sucesso", + "generateFailed": "Falha na geração", + "question": "Pergunta", + "questionPlaceholder": "Por favor, insira a pergunta que deseja fazer...", + "questionRequired": "Por favor, insira a pergunta", + "datasetGenerated": "Conjunto de dados gerado com sucesso", + "autoGenerateQuestions": "Extrair Perguntas Automaticamente", + "autoGenerateConfirm": "O sistema gerará automaticamente perguntas para todas as imagens sem perguntas geradas. Esta operação criará uma tarefa em segundo plano, você pode verificar o progresso no gerenciamento de tarefas.", + "taskCreated": "Tarefa criada com sucesso, processando em segundo plano", + "taskCreateFailed": "Falha ao criar tarefa", + "manualAnnotation": "Anotação Manual", + "annotationTitle": "Anotação de Imagem", + "imageInfo": "Informações da Imagem", + "annotatedCount": "Anotado", + "selectQuestion": "Selecionar ou Criar Pergunta", + "selectQuestionPlaceholder": "Por favor, selecione o modelo de pergunta...", + "universalQuestions": "Perguntas Universais", + "independentQuestions": "Perguntas Independentes", + "answerTypeText": "Texto", + "answerTypeLabel": "Tag", + "answerTypeCustomFormat": "Formato Personalizado", + "usedTimes": "Usado {{count}} vez(es)", + "answer": "Resposta", + "answerPlaceholder": "Por favor, insira a resposta...", + "selectLabels": "Selecionar Tags", + "availableLabels": "Tags Disponíveis", + "noLabelsAvailable": "Nenhuma tag disponível ainda", + "addNewLabel": "Adicionar nova tag...", + "selectedLabels": "Selecionado", + "customFormatAnswer": "Resposta em Formato Personalizado", + "formatRequirement": "Requisito de Formato", + "customFormatPlaceholder": "Por favor, insira JSON no formato correto...", + "note": "Observação", + "notePlaceholder": "Informações de observação (opcional)", + "saveAndContinue": "Salvar e Continuar", + "noImageSelected": "Nenhuma imagem selecionada", + "noTemplateSelected": "Por favor, selecione a pergunta", + "answerRequired": "Por favor, insira a resposta", + "invalidJsonFormat": "Formato JSON incorreto", + "annotationSuccess": "Anotação salva com sucesso", + "annotationFailed": "Falha ao salvar anotação", + "allQuestionsAnnotated": "Todas as perguntas da imagem atual foram anotadas", + "allImagesAnnotated": "Todas as perguntas de todas as imagens foram anotadas", + "noQuestionsAssociated": "Nenhuma pergunta associada à imagem atual", + "loadImageDetailFailed": "Falha ao carregar detalhes da imagem", + "answeredQuestions": "Perguntas Respondidas", + "useTemplate": "Usar Modelo", + "formatJson": "Formatar", + "jsonFormatHelp": "Por favor, insira dados em formato JSON válido", + "imageLoadError": "Falha ao carregar imagem", + "annotate": "Anotar", + "annotateImage": "Anotar Imagem", + "createQuestion": "Criar Pergunta", + "createTemplate": "Criar Modelo de Pergunta", + "aiGenerate": "Reconhecimento de IA", + "aiGenerateSuccess": "Geração de IA bem-sucedida", + "aiGenerateFailed": "Falha na geração de IA", + "missingParameters": "Parâmetros necessários ausentes", + "selectNewQuestion": "Selecionar Nova Pergunta", + "fetchTemplatesFailed": "Falha ao obter modelos de pergunta", + "createTemplateSuccess": "Modelo de pergunta criado com sucesso", + "createTemplateFailed": "Falha ao criar modelo de pergunta", + "updateTemplateSuccess": "Modelo de pergunta atualizado com sucesso", + "updateTemplateFailed": "Falha ao atualizar modelo de pergunta", + "deleteTemplateSuccess": "Modelo de pergunta excluído com sucesso", + "deleteTemplateFailed": "Falha ao excluir modelo de pergunta", + "template": { + "management": "Gerenciar Modelos de Pergunta", + "create": "Criar Modelo", + "edit": "Editar Modelo", + "question": "Conteúdo da Pergunta", + "description": "Descrição", + "noTemplates": "Nenhum modelo de pergunta ainda, clique no botão criar para adicionar", + "deleteConfirm": "Tem certeza de que deseja excluir este modelo de pergunta?", + "used": "Usado", + "addLabel": "Adicionar Tag", + "customFormat": "Formato Personalizado", + "customFormatHelp": "Insira restrições de saída no formato JSON", + "customFormatInfo": "Este formato será fornecido como prompt ao modelo grande, usado para restringir o formato de saída", + "type": { + "label": "Tipo de Pergunta", + "universal": "Pergunta Universal", + "independent": "Pergunta Independente" + }, + "answerType": { + "label": "Tipo de Resposta", + "text": "Texto", + "tags": "Tags", + "customFormat": "Formato Personalizado" + }, + "errors": { + "questionRequired": "Por favor, insira o conteúdo da pergunta", + "labelsRequired": "Perguntas do tipo tag precisam de pelo menos uma tag", + "customFormatRequired": "Por favor, insira o formato personalizado", + "invalidJson": "Formato JSON incorreto" + } + } + }, + "monitoring": { + "title": "Painel de Monitoramento de Recursos", + "timeRange": { + "24h": "24 Horas", + "7d": "Últimos 7 Dias", + "30d": "Últimos 30 Dias" + }, + "filters": { + "allProjects": "Todos os Projetos", + "allProviders": "Todos os Provedores", + "allStatus": "Todos os Status" + }, + "status": { + "success": "Sucesso", + "failed": "Falha" + }, + "actions": { + "export": "Exportar Relatório" + }, + "stats": { + "totalTokens": "Consumo Total de Tokens", + "avgTokensPerCall": "Consumo Médio de Tokens/Chamada", + "totalCalls": "Total de Chamadas", + "avgLatency": "Tempo Médio de Resposta", + "inputOutput": "Entrada: {{input}} · Saída: {{output}}", + "successCalls": "{{count}} sucesso", + "failedCalls": "{{count}} falha", + "failureRate": "{{rate}}% taxa de falha", + "basedOnSuccessCalls": "Baseado em {{count}} solicitações bem-sucedidas", + "noSuccessCalls": "Nenhuma solicitação bem-sucedida ainda" + }, + "charts": { + "tokenTrend": "Tendência de Consumo de Tokens", + "inputLegend": "Entrada", + "outputLegend": "Saída", + "distributionTitle": "Distribuição de Consumo de Tokens (por Modelo)", + "distributionSubtitle": "Proporção de consumo de recursos de diferentes modelos", + "tokensTooltip": "{{value}}K Tokens" + }, + "table": { + "title": "Detalhes de Uso Detalhados", + "searchPlaceholder": "Pesquisar projeto, modelo ou motivo de falha...", + "empty": "Nenhum dado ainda", + "rowsPerPage": "Linhas por página:", + "columns": { + "projectName": "Nome do Projeto", + "provider": "Provedor do Modelo", + "model": "Nome do Modelo", + "status": "Status", + "failureReason": "Motivo da Falha", + "inputTokens": "TOKEN de Entrada", + "outputTokens": "TOKEN de Saída", + "totalTokens": "TOTAL", + "calls": "Número de Chamadas", + "avgLatency": "Tempo Médio" + } + }, + "errors": { + "fetchSummaryFailed": "Falha ao obter dados resumidos de monitoramento", + "fetchLogsFailed": "Falha ao obter logs de monitoramento" + } + }, + "eval": { + "title": "Avaliação", + "datasets": "Conjuntos de Dados de Avaliação", + "tasks": "Tarefas de Avaliação Automática", + "datasetsTitle": "Conjuntos de Dados de Avaliação", + "datasetsDescription": "Gerenciar e visualizar todas as questões de teste de avaliação geradas", + "tasksTitle": "Tarefas de Avaliação", + "tasksComingSoon": "Funcionalidade em desenvolvimento", + "tasksComingSoonHint": "A funcionalidade de tarefas de avaliação será lançada em breve, fique atento", + "totalQuestions": "Total de Questões", + "questionType": "Tipo de Questão", + "question": "Questão", + "answer": "Resposta", + "options": "Opções", + "correct": "Correto", + "wrong": "Errado", + "sourceChunk": "Bloco de Texto de Origem", + "tags": "Tags", + "tagsPlaceholder": "Insira tags, múltiplas tags separadas por vírgula", + "note": "Observação", + "detail": "Detalhes", + "notFound": "Questão não encontrada", + "noData": "Nenhum dado de avaliação ainda", + "noDataHint": "Por favor, gere primeiro o conjunto de teste de avaliação na página de divisão de texto", + "searchPlaceholder": "Pesquisar conteúdo da questão...", + "cardView": "Visualização em Cartões", + "listView": "Visualização em Lista", + "deleteSelected": "Excluir Selecionados ({{count}})", + "deleteConfirmTitle": "Confirmar Exclusão", + "deleteConfirmMessage": "Tem certeza de que deseja excluir {{count}} questão(ões)? Esta ação não pode ser desfeita.", + "questionTypes": { + "true_false": "Verdadeiro/Falso", + "single_choice": "Múltipla Escolha", + "multiple_choice": "Múltipla Escolha (Várias Respostas)", + "short_answer": "Resposta Curta", + "open_ended": "Questão Aberta" + } + }, + "evalDatasets": { + "import": { + "title": "Importar Conjunto de Dados de Avaliação", + "questionType": "Tipo de Questão", + "selectTypeFirst": "Por favor, selecione o tipo de questão primeiro", + "selectFile": "Por favor, selecione o arquivo a ser importado", + "invalidFileType": "Formato de arquivo não suportado, por favor, faça upload de arquivo json, xls ou xlsx", + "formatPreview": "Pré-visualização do Formato de Dados", + "downloadTemplate": "Baixar Modelo", + "template": "Modelo", + "uploadFile": "Enviar Arquivo", + "dropOrClick": "Clique ou arraste o arquivo para cá", + "supportedFormats": "Suporta formatos JSON, XLS, XLSX", + "tags": "Tags (opcional)", + "tagsPlaceholder": "Adicionar tags aos dados importados, múltiplas tags separadas por vírgula", + "tagsHelp": "Todos os dados importados serão marcados com estas tags", + "import": "Importar", + "importing": "Importando...", + "failed": "Falha na importação", + "success": "Importação bem-sucedida", + "successMessage": "{{count}} dados de avaliação importados com sucesso", + "showingErrors": "Exibindo primeiros {{count}} erros", + "custom": "Importar Conjunto de Dados Personalizado", + "builtin": "Importar Conjunto de Dados Integrado", + "builtinTitle": "Selecionar Conjunto de Dados Integrado", + "searchPlaceholder": "Pesquisar conjunto de dados...", + "confirmImportTitle": "Confirmar Importação", + "confirmImportMessage": "Tem certeza de que deseja importar o conjunto de dados \"{{name}}\"? Isso adicionará novos dados de avaliação ao projeto atual.", + "downloading": "Baixando..." + }, + "export": { + "title": "Exportar Conjunto de Dados de Avaliação", + "formatLabel": "Formato de Exportação", + "filterLabel": "Condições de Filtro", + "previewLabel": "Dados a serem exportados:", + "records": "registros", + "largeDataHint": "Grande volume de dados, será usada exportação em streaming, por favor, aguarde pacientemente", + "exporting": "Exportando...", + "exportBtn": "Exportar", + "jsonDesc": "Array JSON padrão", + "jsonlDesc": "Um registro por linha", + "csvDesc": "Formato de tabela", + "noTagsAvailable": "Nenhuma tag disponível" + } + }, + "evalTasks": { + "title": "Tarefas de Avaliação de Modelo", + "createTitle": "Criar Tarefa de Avaliação", + "detailTitle": "Detalhes da Tarefa de Avaliação", + "createTask": "Criar Tarefa", + "noTasks": "Nenhuma tarefa de avaliação ainda", + "noTasksHint": "Crie tarefas de avaliação para testar o desempenho do modelo no conjunto de dados de avaliação", + "selectModels": "Selecionar Modelos de Teste", + "selectModelsHint": "Pode selecionar múltiplos modelos para avaliação comparativa", + "selectJudgeModel": "Selecionar Modelo Professor", + "selectJudgeModelPlaceholder": "Por favor, selecione...", + "selectJudgeModelHint": "O modelo professor é usado para avaliar questões de resposta curta e questões abertas, não pode ser o mesmo que o modelo de teste", + "judgeModel": "Modelo Professor", + "filterByType": "Filtrar por Tipo de Questão", + "filterByTypeHint": "Se não selecionar, usará todas as questões", + "selectedQuestions": "Questões Selecionadas", + "questions": "questões", + "hasSubjectiveHint": "Inclui questões subjetivas (resposta curta/aberta), precisa selecionar modelo professor para pontuação", + "hasSubjective": "Inclui Questões Subjetivas", + "startEval": "Iniciar Avaliação", + "progress": "Progresso", + "totalQuestions": "Quantidade de Questões", + "status": "Status", + "totalScore": "Pontuação Total", + "correctCount": "Quantidade de Acertos", + "accuracy": "Taxa de Acerto", + "statsByType": "Estatísticas por Tipo de Questão", + "resultDetails": "Detalhes dos Resultados da Avaliação", + "question": "Questão", + "questionType": "Tipo de Questão", + "result": "Resultado", + "score": "Pontuação", + "correctAnswer": "Resposta Correta", + "modelAnswer": "Resposta do Modelo", + "judgeResponse": "Pontuação do Modelo Professor", + "interrupt": "Interromper Tarefa", + "statusProcessing": "Em Andamento", + "statusCompleted": "Concluído", + "statusFailed": "Falhou", + "statusInterrupted": "Interrompido", + "deleteConfirmTitle": "Confirmar Exclusão", + "deleteConfirmMessage": "Tem certeza de que deseja excluir esta tarefa de avaliação? Esta ação também excluirá todos os resultados de avaliação.", + "interruptConfirmTitle": "Confirmar Interrupção", + "interruptConfirmMessage": "Tem certeza de que deseja interromper esta tarefa de avaliação? Os resultados de avaliação concluídos serão retidos.", + "errorNoModels": "Por favor, selecione pelo menos um modelo de teste", + "errorNoQuestions": "Nenhuma questão de avaliação disponível", + "errorNoJudgeModel": "Existem questões subjetivas, por favor, selecione um modelo professor para pontuação", + "errorJudgeSameAsTest": "O modelo professor não pode ser o mesmo que o modelo de teste", + "errorCreateFailed": "Falha ao criar tarefa de avaliação", + "errorLoadFailed": "Falha ao carregar tarefa de avaliação", + "errorDeleteFailed": "Falha ao excluir tarefa de avaliação", + "errorInterruptFailed": "Falha ao interromper tarefa de avaliação", + "statusSuccess": "Sucesso", + "statusFormatError": "Erro de Formato", + "statusApiError": "Erro de API", + "statusUnknown": "Status Desconhecido", + "duration": "Duração", + "answerStatus": "Status da Resposta", + "modelInfo": "Informações do Modelo", + "reportTitle": "Relatório de Avaliação de Capacidade do Modelo", + "taskIdLabel": "ID da Tarefa", + "pageInfo": "Página {{page}} / {{totalPages}}", + "noMatchingResults": "Nenhum resultado de avaliação correspondente ainda", + "reportFooter": "Easy Dataset Evaluation System · Gerado por IA", + "finalSelection": "Seleção Final:", + "questionsSuffix": "questões", + "noModelsAvailable": "Nenhum modelo disponível ainda, por favor, configure o modelo nas configurações primeiro", + "filterTitle": "Filtro de Questões", + "clearFilter": "Limpar Filtro", + "searchKeyword": "Pesquisar Palavra-chave", + "searchPlaceholder": "Pesquisar conteúdo da questão ou resposta...", + "filterByTypeLabel": "Filtro por Tipo", + "filterByTagLabel": "Filtro por Tag", + "questionCountLabel": "Quantidade de Questões:", + "useAllQuestions": "Usar Todos os Resultados do Filtro", + "randomSampleHint": "Será extraída aleatoriamente {{questionCount}} questão(ões) de {{filteredCount}}", + "durationFormat": "(Duração {{time}}s)", + "totalQuestionsLabel": "Total de Questões", + "correctLabel": "Correto", + "incorrectLabel": "Incorreto", + "judgeComment": "Comentário do Professor de IA:", + "scoreUnit": "pontos", + "shortAnswer": "Resposta Curta", + "openEnded": "Questão Aberta", + "scoreAnchorsTitle": "Regras de Pontuação para {{type}}", + "customizable": "Personalizável", + "scoreAnchorsHint": "Personalizar critérios de pontuação, usados para orientar o LLM na avaliação da qualidade das respostas do modelo", + "restoreDefault": "Restaurar Padrão", + "scoreRange": "Intervalo de Pontuação", + "scoreDescriptionPlaceholder": "Por favor, insira a descrição do critério de pontuação para este intervalo..." + }, + "blindTest": { + "title": "Tarefas de Teste Cego Manual", + "createTitle": "Criar Tarefa de Teste Cego", + "createTask": "Criar Tarefa", + "noTasks": "Nenhuma tarefa de teste cego ainda", + "noTasksHint": "Crie tarefas de teste cego para comparar a qualidade das respostas de dois modelos", + "selectModels": "Selecionar Modelos para Comparação", + "modelA": "Modelo A", + "modelB": "Modelo B", + "modelComparison": "Comparação de Modelos", + "selectQuestions": "Selecionar Questões de Teste", + "questionType": "Tipo de Questão", + "questionTypeHint": "Tarefas de teste cego suportam apenas questões de resposta curta e questões abertas", + "filterByTag": "Filtrar por Tag", + "questionCount": "Quantidade de Questões", + "availableQuestions": "Questões Disponíveis: {{count}}", + "useAllQuestions": "Usar Todos os Resultados do Filtro", + "randomSample": "Será extraída aleatoriamente {{count}} questão(ões)", + "startBlindTest": "Iniciar Teste Cego", + "creating": "Criando...", + "noModelsAvailable": "Nenhum modelo disponível ainda, por favor, configure o modelo nas configurações primeiro", + "errorSelectModelA": "Por favor, selecione o Modelo A", + "errorSelectModelB": "Por favor, selecione o Modelo B", + "errorSameModel": "Os dois modelos não podem ser iguais", + "errorNoQuestions": "Nenhuma questão correspondente", + "statusProcessing": "Em Andamento", + "statusCompleted": "Concluído", + "statusFailed": "Falhou", + "statusInterrupted": "Interrompido", + "progress": "Progresso", + "viewDetails": "Ver Detalhes", + "continue": "Continuar Teste Cego", + "interrupt": "Interromper Tarefa", + "deleteConfirmTitle": "Confirmar Exclusão", + "deleteConfirmMessage": "Tem certeza de que deseja excluir esta tarefa de teste cego? Esta ação não pode ser desfeita.", + "interruptConfirmTitle": "Confirmar Interrupção", + "interruptConfirmMessage": "Tem certeza de que deseja interromper esta tarefa de teste cego? Os resultados de avaliação concluídos serão retidos.", + "inProgress": "Teste Cego em Andamento", + "generatingAnswers": "Gerando respostas...", + "question": "Questão", + "answerA": "Resposta A", + "answerB": "Resposta B", + "duration": "Duração", + "whichBetter": "Qual resposta é melhor?", + "leftBetter": "Esquerda é Melhor", + "rightBetter": "Direita é Melhor", + "bothGood": "Ambas são Boas", + "bothBad": "Ambas são Ruins", + "loadQuestion": "Carregar Questão", + "taskNotFound": "Tarefa não existe", + "resultTitle": "Resultados do Teste Cego", + "resultSummary": "Resumo dos Resultados de Avaliação", + "wins": "Vitórias", + "times": "vezes", + "totalQuestions": "Total de Questões", + "ties": "Empates", + "detailResults": "Resultados Detalhados", + "left": "Esquerda", + "right": "Direita" + } +} \ No newline at end of file diff --git a/easy-dataset-main/locales/tr/translation.json b/easy-dataset-main/locales/tr/translation.json new file mode 100644 index 0000000..1124a79 --- /dev/null +++ b/easy-dataset-main/locales/tr/translation.json @@ -0,0 +1,1506 @@ +{ + "language": { + "switchToEnglish": "İngilizce'ye Geç", + "switchToChinese": "Çince'ye Geç", + "switchToTurkish": "Türkçe'ye Geç", + "switcherTitle": "Dil Değiştir / Change Language / 切换语言", + "english": "İngilizce", + "chineseSimplified": "Basitleştirilmiş Çince", + "turkish": "Türkçe", + "portugues": "Portugês", + "en": "EN", + "zh": "中", + "tr": "TR" + }, + "theme": { + "switchToLight": "Açık Moda Geç", + "switchToDark": "Koyu Moda Geç" + }, + "settings": { + "promptConfig": "İstem Yapılandırması", + "promptsDescription": "Projede kullanılan istemlerinizi yapılandırın, global istemler ve senaryo özel istemler desteklenir.", + "globalPrompt": "Global İstem", + "questionPrompt": "Soru Üretme İstemi", + "answerPrompt": "Cevap Üretme İstemi", + "labelPrompt": "Soru Etiketleme İstemi", + "domainTreePrompt": "Alan Ağacı Oluşturma İstemi", + "globalPromptPlaceholder": "Tüm senaryolar için temel istem olacak global istemi girin", + "questionPromptPlaceholder": "Soru üretmek için istemi girin", + "answerPromptPlaceholder": "Cevap üretmek için istemi girin", + "labelPromptPlaceholder": "Soru etiketlemek için istemi girin (şu anda desteklenmiyor)", + "domainTreePromptPlaceholder": "Alan ağacı oluşturmak için istemi girin", + "cleanPrompt": "Veri Temizleme İstemi", + "cleanPromptPlaceholder": "Veri temizleme için özel istem girin", + "loadPromptsFailed": "İstem yapılandırmaları yüklenemedi", + "savePromptsSuccess": "İstem yapılandırmaları başarıyla kaydedildi", + "savePromptsFailed": "İstem yapılandırmaları kaydedilemedi", + "title": "Ayarlar", + "basicInfo": "Temel Bilgiler", + "modelConfig": "Model Yapılandırması", + "taskConfig": "Görev Yapılandırması", + "tabsAriaLabel": "Ayarlar Sekmeleri", + "idNotEditable": "Proje ID'si düzenlenemez", + "saveBasicInfo": "Temel Bilgileri Kaydet", + "saveSuccess": "Başarıyla Kaydedildi", + "saveFailed": "Kaydetme Başarısız", + "deleteSuccess": "Başarıyla Silindi", + "deleteFailed": "Silme Başarısız", + "fetchTasksFailed": "Görev ayarları getirilemedi", + "saveTasksFailed": "Görev ayarları kaydedilemedi", + "textSplitSettings": "Metin Bölme Ayarları", + "minLength": "Minimum Uzunluk", + "maxLength": "Maksimum Bölme Uzunluğu", + "textSplitDescription": "Metin bölme uzunluk aralığını ayarlayın", + "splitType": "Bölme Stratejisi", + "splitTypeMarkdown": "Belge Yapısı Bölme (Markdown)", + "splitTypeMarkdownDesc": "Metni belgedeki başlıklara göre otomatik olarak bölerek anlamsal bütünlüğü korur. Net yapıya sahip Markdown belgeleri için uygundur.", + "splitTypeRecursive": "Metin Yapısı Bölme (Özel Ayırıcı)", + "splitTypeRecursiveDesc": "Çok seviyeli ayırıcıları (yapılandırılabilir) özyinelemeli olarak dener. Önce yüksek öncelikli ayırıcıları, sonra ikincil ayırıcıları kullanır. Karmaşık belgeler için uygundur.", + "splitTypeText": "Sabit Uzunlukta Bölme (Karakter)", + "splitTypeTextDesc": "Metni belirtilen ayırıcıya (yapılandırılabilir) göre böler, ardından belirtilen uzunluğa göre birleştirir. Sıradan metin dosyaları için uygundur.", + "splitTypeToken": "Sabit Uzunlukta Bölme (Token)", + "splitTypeTokenDesc": "Token sayısına göre (karakter sayısı değil) bloklar.", + "splitTypeCode": "Program Kodu Akıllı Bölme", + "splitTypeCodeDesc": "Farklı programlama dillerinin sözdizimi yapısına göre akıllıca bloklar, sözdiziminin eksik olduğu yerlerde bölmekten kaçınır.", + "splitTypeCustom": "Özel Sembol Bölme", + "splitTypeCustomDesc": "Belgeleri özel sembollere göre böler. Ayırıcı atılır ve bölünen metin blokları blok boyutundan etkilenmez.", + "codeLanguage": "Programlama Dili", + "codeLanguageHelper": "Dil sözdizimi tabanlı daha akıllı kod bölme için programlama dilini seçin.", + "chunkSize": "Blok Boyutu", + "chunkOverlap": "Blok Örtüşmesi", + "separator": "Ayırıcı", + "separatorHelper": "Metni bölmek için kullanılan ayırıcı, örn. \\n\\n boş satırlar için", + "customSeparator": "Özel Ayırıcı", + "customSeparatorHelper": "Metni bölmek için kullanılan özel ayırıcı, örn. --- veya ===", + "separators": "Ayırıcılar Listesi", + "separatorsInput": "Ayırıcılar (virgülle ayrılmış)", + "separatorsHelper": "Öncelik sırasına göre virgülle ayrılmış ayırıcılar listesi", + "questionGenSettings": "Soru Üretme Ayarları", + "questionGenLength": "Soru Üretme Uzunluğu: {{length}}", + "questionMaskRemovingProbability": "Soru İşaretlerini Kaldırma Olasılığı: {{probability}}%", + "questionGenDescription": "Üretilen sorular için maksimum uzunluğu ayarlayın", + "huggingfaceSettings": "Hugging Face Ayarları", + "datasetUpload": "Veri Seti Yükleme Ayarları", + "huggingfaceToken": "Hugging Face Token", + "huggingfaceNotImplemented": "", + "concurrencyLimit": "Eşzamanlılık Sınırı", + "concurrencyLimitHelper": "Soru üretme ve veri seti üretme görevlerinin eşzamanlı sayısını sınırlayın.", + "saveTaskConfig": "Görev Yapılandırmasını Kaydet", + "pdfSettings": "PDF dosya dönüştürme yapılandırması", + "minerUToken": "MinerU Token yapılandırması", + "minerUHelper": "MinerU Token sadece 14 gün geçerlidir. Lütfen Token'ı zamanında değiştirin", + "minerULocalUrl": "PDF Dönüştürme (MinerU Local) URL Yapılandırması", + "vision": "Özel büyük ölçekli görüş modeli yapılandırması", + "visionConcurrencyLimit": "Özel büyük ölçekli görüş modelleri için eşzamanlılık sınırı", + "prompts": { + "selectPromptFirst": "Lütfen soldan bir istem seçin", + "customized": "Özelleştirilmiş", + "editPrompt": "İstemi Düzenle", + "restoreDefault": "Varsayılana Geri Dön", + "promptType": "İstem Türü", + "keyName": "Anahtar Adı", + "contentPlaceholder": "Lütfen özel istem içeriğini girin...", + "restoreDefaultContent": "Varsayılan İçeriği Geri Yükle", + "noPromptsAvailable": "Kullanılabilir istem yok", + "restoreSuccess": "Varsayılan isteme başarıyla geri dönüldü", + "restoreFailed": "Varsayılan isteme geri dönülemedi", + "deleteError": "İstem silinirken hata:", + "saveSuccess": "İstem başarıyla kaydedildi", + "saveFailed": "İstem kaydedilemedi", + "saveError": "İstem kaydedilirken hata:", + "createCustomPrompt": "Özel İstem Oluştur", + "fetchContentError": "En son istem içeriği getirilemedi:" + }, + "multiTurnSettings": "Çok Turlu Konuşma Ayarları", + "multiTurnSystemPrompt": "Sistem İstemi", + "multiTurnSystemPromptHelper": "Çok turlu konuşma üretimi için sistem istemi", + "multiTurnScenario": "Konuşma Senaryosu", + "multiTurnScenarioHelper": "Konuşma senaryosunu veya bağlamını açıklayın", + "multiTurnRounds": "Tur Sayısı", + "multiTurnRoleA": "Rol A", + "multiTurnRoleAHelper": "Konuşmadaki ilk katılımcının açıklaması", + "multiTurnRoleB": "Rol B", + "multiTurnRoleBHelper": "Konuşmadaki ikinci katılımcının açıklaması", + "multiTurnDescription": "Çok turlu konuşma üretim yapılandırması" + }, + "questions": { + "autoGenerateDataset": "Veri Setini Otomatik Oluştur", + "autoGenerateDatasetTip": "Arka plan toplu işleme görevi oluştur: bekleyen soru üretimi olan metin bloklarını otomatik olarak sorgula ve soruları çıkar.", + "filterAll": "Tüm Sorular", + "filterAnswered": "Cevaplı", + "filterUnanswered": "Cevapsız", + "filterChunkNamePlaceholder": "Blok adına göre filtrele...", + "sourceTypeAll": "Tüm Kaynaklar", + "sourceTypeText": "Metin Kaynağı", + "sourceTypeImage": "Görsel Kaynağı", + "title": "Sorular", + "confirmDeleteTitle": "Soruyu Silmeyi Onayla", + "confirmDeleteContent": "\"{{question}}\" sorusunu silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "deleting": "Soru siliniyor...", + "batchDeleteTitle": "Toplu Silmeyi Onayla", + "batchDeleting": "{{count}} soru siliniyor...", + "deleteSuccess": "Soru başarıyla silindi", + "deleteFailed": "Soru silinemedi", + "batchDeleteSuccess": "{{count}} soru başarıyla silindi", + "batchDeletePartial": "Silme tamamlandı, başarılı: {{success}}, başarısız: {{failed}}", + "batchDeleteFailed": "Sorular toplu olarak silinemedi", + "noQuestionsSelected": "Lütfen önce soruları seçin", + "batchGenerateStart": "{{count}} soru için veri setleri oluşturuluyor", + "invalidQuestionKey": "Geçersiz soru anahtarı", + "listView": "Soru Listesi", + "treeView": "Alan Ağacı", + "selectAll": "Tümünü Seç", + "selectedCount": "{{count}} soru seçildi", + "totalCount": "Toplam {{count}} soru", + "searchPlaceholder": "Soruları veya etiketleri ara...", + "searchModeInclude": "Dahil et", + "searchModeExclude": "Hariç tut", + "searchTargetAll": "Soru+Etiket", + "searchTargetQuestion": "Soru", + "searchTargetTag": "Etiket", + "deleteSelected": "Seçilenleri Sil", + "batchGenerate": "Toplu Veri Seti Oluştur", + "generating": "Veri Seti Oluşturuluyor", + "generatingProgress": "Tamamlanan: {{completed}}/{{total}}", + "generatedCount": "Oluşturulan: {{count}}", + "pleaseWait": "Lütfen bekleyin, işleniyor...", + "createSuccess": "Soru başarıyla oluşturuldu", + "updateSuccess": "Soru başarıyla güncellendi", + "operationSuccess": "İşlem başarılı", + "operationFailed": "İşlem başarısız", + "editQuestion": "Soruyu Düzenle", + "questionContent": "Soru İçeriği", + "sourceType": "Veri Kaynağı Türü", + "sourceType.text": "Metin", + "sourceType.image": "Görsel", + "selectChunk": "Metin Bloğu Seç", + "searchChunk": "Metin bloklarını ara...", + "selectImage": "Görsel Seç", + "searchImage": "Görselleri ara...", + "selectTag": "Etiket Seç", + "searchTag": "Etiketleri ara...", + "createQuestion": "Soru Oluştur", + "createNormalQuestion": "Normal Soru Oluştur", + "createQuestionTemplate": "Soru Şablonu Oluştur", + "questionPlaceholder": "Lütfen sorunuzu girin", + "noChunkSelected": "Lütfen önce bir metin bloğu seçin", + "noTagSelected": "Lütfen bir etiket seçin", + "fetchTemplatesFailed": "Soru şablonları getirilemedi", + "createTemplateSuccess": "Soru şablonu başarıyla oluşturuldu", + "createTemplateFailed": "Soru şablonu oluşturulamadı", + "updateTemplateSuccess": "Soru şablonu başarıyla güncellendi", + "updateTemplateFailed": "Soru şablonu güncellenemedi", + "deleteTemplateSuccess": "Soru şablonu başarıyla silindi", + "deleteTemplateFailed": "Soru şablonu silinemedi", + "template": { + "management": "Soru Şablonları", + "create": "Şablon Oluştur", + "edit": "Şablonu Düzenle", + "question": "Soru İçeriği", + "description": "İstem", + "descriptionHelp": "Bu soru şablonuyla ilgili AI cevapları üretilirken genel isteme dahil edilir, nihai cevap üretim sonuçlarını etkilemek için kullanılır", + "noTemplates": "Henüz soru şablonu yok, eklemek için oluştur butonuna tıklayın", + "deleteConfirm": "Bu soru şablonunu silmek istediğinizden emin misiniz?", + "used": "Kullanıldı", + "addLabel": "Etiket Ekle", + "customFormat": "Özel Format", + "customFormatHelp": "JSON format çıktı kısıtlaması girin", + "customFormatInfo": "Bu format, çıktı formatını kısıtlamak için LLM'e istem olarak sağlanacaktır", + "sourceTypeInfo": "Veri Kaynağı Türü", + "sourceType": { + "label": "Veri Kaynağı Türü", + "image": "Görsel", + "text": "Metin" + }, + "answerType": { + "label": "Cevap Türü", + "text": "Metin", + "tags": "Etiketler", + "customFormat": "Özel Format" + }, + "errors": { + "questionRequired": "Lütfen soru içeriğini girin", + "labelsRequired": "Etiket türü sorular en az bir etiket gerektirir", + "customFormatRequired": "Lütfen özel format girin", + "invalidJson": "Geçersiz JSON formatı" + }, + "autoGenerate": "Şablon oluşturduktan sonra otomatik soru oluştur", + "autoGenerateHelpText": "Projedeki tüm metin blokları için bu şablona dayalı sorular otomatik olarak oluşturulacaktır", + "autoGenerateHelpImage": "Projedeki tüm görseller için bu şablona dayalı sorular otomatik olarak oluşturulacaktır", + "confirmAutoGenerate": "Otomatik Soru Oluşturmayı Onayla", + "confirmAutoGenerateTextMessage": "Otomatik soru oluşturmayı seçtiniz. Sistem, projedeki tüm metin blokları için bu şablona dayalı sorular oluşturacaktır.", + "confirmAutoGenerateImageMessage": "Otomatik soru oluşturmayı seçtiniz. Sistem, projedeki tüm görseller için bu şablona dayalı sorular oluşturacaktır.", + "autoGenerateWarning": "Bu işlem çok sayıda soru oluşturabilir. Devam etmek için lütfen onaylayın.", + "autoGenerateSuccess": "{{count}} veri kaynağı için başarıyla soru oluşturuldu", + "autoGeneratePartialFail": "{{success}} soru başarıyla oluşturuldu, {{fail}} başarısız", + "autoGenerateFailed": "Otomatik soru oluşturma başarısız" + }, + "generateSingleTurnDataset": "Tek Turlu Veri Seti Oluştur", + "generateSingleTurnDatasetDesc": "Sorulara dayalı S&C veri seti oluştur", + "generateMultiTurnDataset": "Çok Turlu Veri Seti Oluştur", + "generateImageDataset": "Görsel S&C Veri Seti Oluştur", + "generateMultiTurnDatasetDesc": "Sorulara dayalı çok turlu konuşma veri seti oluştur", + "deleteConfirm": "Bu soruyu silmek istediğinizden emin misiniz? Bu işlem geri alınamaz." + }, + "monitoring": { + "title": "Resource Monitoring Dashboard", + "timeRange": { + "24h": "Last 24 hours", + "7d": "Last 7 days", + "30d": "Last 30 days" + }, + "filters": { + "allProjects": "All Projects", + "allProviders": "All Providers", + "allStatus": "All Status" + }, + "status": { + "success": "Success", + "failed": "Failed" + }, + "actions": { + "export": "Export Report" + }, + "stats": { + "totalTokens": "Total Token Usage", + "avgTokensPerCall": "Avg Tokens per Call", + "totalCalls": "Total Calls", + "avgLatency": "Avg Latency", + "inputOutput": "Input: {{input}} · Output: {{output}}", + "successCalls": "{{count}} Success", + "failedCalls": "{{count}} Failed", + "failureRate": "{{rate}}% Failure Rate", + "basedOnSuccessCalls": "Based on {{count}} successful calls", + "noSuccessCalls": "No successful calls" + }, + "charts": { + "tokenTrend": "Token Usage Trend", + "inputLegend": "Input", + "outputLegend": "Output", + "distributionTitle": "Token Usage Distribution (by Model)", + "distributionSubtitle": "Resource usage share by model", + "tokensTooltip": "{{value}}K Tokens" + }, + "table": { + "title": "Usage Details", + "searchPlaceholder": "Search project, model, or failure reason...", + "empty": "No data", + "rowsPerPage": "Rows per page:", + "columns": { + "projectName": "Project", + "provider": "Provider", + "model": "Model", + "status": "Status", + "failureReason": "Failure Reason", + "inputTokens": "Input Tokens", + "outputTokens": "Output Tokens", + "totalTokens": "Total", + "calls": "Calls", + "avgLatency": "Avg Latency" + } + }, + "errors": { + "fetchSummaryFailed": "Failed to fetch monitoring summary", + "fetchLogsFailed": "Failed to fetch monitoring logs" + } + }, + "common": { + "dataSource": "Veri Kaynağı", + "menu": "Menü", + "openMenu": "Navigasyon menüsünü aç", + "all": "Tümü", + "jumpTo": "Git", + "unknownError": "Bilinmeyen Hata", + "create": "Oluştur", + "edit": "Düzenle", + "delete": "Sil", + "save": "Kaydet", + "cancel": "İptal", + "confirm": "Onayla", + "complete": "Tamamla", + "close": "Kapat", + "add": "Ekle", + "remove": "Kaldır", + "loading": "Yükleniyor...", + "yes": "Evet", + "no": "Hayır", + "confirmDelete": "Silmeyi Onayla", + "saving": "Kaydediliyor...", + "deleting": "Siliniyor...", + "actions": "İşlemler", + "confirmDeleteDataSet": "Bu veri setini silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "noData": "Yok", + "failed": "Başarısız", + "success": "Başarılı", + "backToList": "Listeye Dön", + "label": "Etiket", + "confirmDeleteDescription": "Bu Dosyayı silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "more": "Daha Fazla", + "fetchError": "Veri getirme başarısız", + "confirmDeleteQuestion": "Bu soruyu silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "deleteSuccess": "Başarıyla silindi", + "visitGitHub": "GitHub Deposunu Ziyaret Et", + "syncOldData": "Eski Verileri Senkronize Et", + "copy": "Kopyala", + "enabled": "Etkin", + "disabled": "Devre Dışı", + "copied": "Kopyalandı", + "generating": "Oluşturuluyor...", + "items": "öğe", + "detailInfo": "Detaylı Bilgi" + }, + "home": { + "title": "Easy Dataset", + "subtitle": "Büyük Dil Modelleri için ince ayar veri setleri oluşturmak için güçlü bir araç", + "createProject": "Proje Oluştur", + "searchDataset": "Açık Veri Setlerinde Ara" + }, + "projects": { + "reuseConfig": "Model Yapılandırmasını Yeniden Kullan", + "noReuse": "Yapılandırma Yeniden Kullanma", + "selectProject": "Proje Seç", + "fetchFailed": "Proje listesi getirilemedi", + "fetchError": "Proje listesi getirilirken hata", + "loading": "Projeleriniz yükleniyor...", + "createFailed": "Proje oluşturulamadı", + "createError": "Proje oluşturulurken hata", + "createNew": "Yeni Proje Oluştur", + "saveFailed": "Proje kaydedilemedi", + "id": "Proje ID", + "name": "Proje Adı", + "description": "Proje Açıklaması", + "questions": "Sorular", + "datasets": "Veri Setleri", + "lastUpdated": "Son Güncelleme", + "viewDetails": "Detayları Görüntüle", + "createFirst": "Lütfen önce bir proje oluşturun", + "noProjects": "Proje bulunamadı", + "notExist": "Proje mevcut değil.", + "createProject": "Proje Oluştur", + "deleteConfirm": "Bu projeyi silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "deleteSuccess": "Proje başarıyla silindi", + "deleteFailed": "Proje silinemedi", + "backToHome": "Ana Sayfaya Dön", + "deleteConfirmTitle": "Silmeyi Onayla" + }, + "textSplit": { + "dragToUpload": "Yüklemek için dosyaları sürükleyin", + "fileList": "Dosya Listesi", + "autoGenerateQuestions": "Otomatik Oluştur", + "autoGenerateQuestionsTip": "Arka plan toplu işleme görevi oluştur: bekleyen soru üretimi olan metin bloklarını otomatik olarak sorgula ve soruları çıkar.", + "exportChunks": "Blokları Dışa Aktar", + "allChunks": "Tüm Metin Blokları", + "generatedQuestions2": "Sorulu", + "ungeneratedQuestions": "Sorusuz", + "noFilesUploaded": "Henüz dosya yüklenmedi", + "unknownFile": "Bilinmeyen Dosya", + "fetchFilesFailed": "Dosyalar getirilemedi", + "editTag": "Etiketi Düzenle", + "deleteTag": "Etiketi Sil", + "addTag": "Etiket Ekle", + "selectedCount": "{{count}} metin bloğu seçildi", + "totalCount": "toplam {{count}} metin bloğu", + "batchGenerateQuestions": "Toplu Oluştur", + "uploadedDocuments": "{{count}} Belge Yüklendi", + "title": "Metinler", + "uploadNewDocument": "Yeni Belge Yükle", + "selectFile": "Dosya Seç", + "markdownOnly": "Şu anda yalnızca Markdown (.md) format dosyaları destekleniyor", + "supportedFormats": "Desteklenen formatlar: .pdf .md, .txt, .docx", + "uploadAndProcess": "Yükle ve İşle", + "selectedFiles": "Seçilen Dosyalar ({{count}})", + "oneFileMessage": "Dosya yüklenemez, lütfen önce mevcut dosyayı silin", + "mutilFileMessage": "Yeni dosya yüklendikten sonra alan ağacı yeniden oluşturulacak", + "noChunks": "Metin bloğu bulunamadı", + "chunkDetails": "Blok Detayları: {{chunkId}}", + "fetchChunksFailed": "Metin blokları getirilemedi", + "fetchChunksError": "Metin blokları getirilirken hata", + "fileResultReceived": "Dosya sonucu alındı", + "fileUploadSuccess": "Dosya başarıyla yüklendi", + "splitTextFailed": "Metin bölme başarısız", + "splitTextError": "Metin bölme hatası", + "deleteChunkFailed": "Metin bloğu silinemedi", + "deleteChunkError": "Metin bloğu silinirken hata", + "selectModelFirst": "Lütfen önce bir model seçin, üst gezinme çubuğundan seçebilirsiniz", + "modelNotAvailable": "Seçilen model kullanılamıyor, lütfen tekrar seçin", + "generateQuestionsFailed": "{{chunkId}} bloğu için soru üretilemedi", + "questionsGenerated": "{{total}} soru üretildi", + "customSplitMode": "Özel Bölme Modu", + "customSplitInstructions": "Bölme noktaları eklemek için metin seçin. Sistem, seçtiğiniz konumlara bölme işaretleyicileri yerleştirecektir.", + "splitPointsList": "Eklenen Bölme Noktaları", + "saveSplitPoints": "Bölme Noktalarını Kaydet", + "confirmCustomSplitTitle": "Bölme Değişimini Onayla", + "confirmCustomSplitMessage": "Not: Özel bölme noktaları, bu belge için önceki otomatik bölme sonuçlarının yerini alacaktır. Devam etmek istiyor musunuz?", + "customSplitSuccess": "Özel bölme başarıyla kaydedildi", + "customSplitFailed": "Özel bölme kaydedilemedi", + "missingRequiredData": "Gerekli veri eksik", + "chunksPreview": "Blok Boyutu Önizlemesi", + "chunk": "Blok", + "characters": " karakter", + "questionsGeneratedSuccess": "Metin bloğu için başarıyla {{total}} soru üretildi", + "generateQuestionsForChunkFailed": "{{chunkId}} bloğu için soru üretilemedi", + "generateQuestionsForChunkError": "{{chunkId}} bloğu için soru üretilirken hata", + "generateQuestionsError": "Soru üretilirken hata", + "partialSuccess": "Kısmi başarılı soru üretimi ({{successCount}}/{{total}}), {{errorCount}} blok başarısız", + "allSuccess": "{{successCount}} metin bloğu için başarıyla {{totalQuestions}} soru üretildi", + "fileDeleted": "{{fileName}} dosyası silindi, metin bloğu listesi yenileniyor", + "tabs": { + "smartSplit": "Akıllı Bölme", + "domainAnalysis": "Alan Analizi" + }, + "loading": "Yükleniyor...", + "fetchingDocuments": "Belge verileri getiriliyor", + "processing": "İşleniyor...", + "progressStatus": "{{total}} metin bloğu seçildi, {{completed}} tamamlandı", + "processingPleaseWait": "İşleniyor, lütfen bekleyin!", + "oneFileLimit": "Dosya yüklenemez, zaten yüklenmiş bir dosya var", + "unsupportedFormat": "Desteklenmeyen dosya formatı: {{files}}", + "modelInfoParseError": "Model bilgisi ayrıştırılamadı", + "uploadFailed": "Yükleme başarısız", + "uploadSuccess": "{{count}} dosya başarıyla yüklendi", + "deleteFailed": "Dosya silinemedi", + "deleteSuccess": "{{fileName}} dosyası başarıyla silindi", + "generatedQuestions": "{{count}} Soru", + "viewDetails": "Detayları Görüntüle", + "generateQuestions": "Soru Üret", + "dataCleaning": "Veri Temizleme", + "batchDataCleaning": "Toplu Veri Temizleme", + "autoDataCleaning": "Otomatik Veri Temizleme", + "autoDataCleaningTip": "Arka plan toplu işleme görevi oluştur: tüm metin bloklarını otomatik olarak temizle", + "dataCleaningSuccess": "Veri temizleme tamamlandı, orijinal uzunluk: {{originalLength}}, temizlenmiş uzunluk: {{cleanedLength}}", + "dataCleaningFailed": "{{chunkId}} metin bloğu için veri temizleme başarısız", + "dataCleaningForChunkSuccess": "{{chunkId}} metin bloğu için veri temizleme tamamlandı", + "dataCleaningForChunkFailed": "{{chunkId}} metin bloğu için veri temizleme başarısız", + "dataCleaningForChunkError": "{{chunkId}} metin bloğu için veri temizleme hatası", + "dataCleaningPartialSuccess": "Kısmi veri temizleme başarısı ({{successCount}}/{{total}}), {{errorCount}} metin bloğu başarısız", + "dataCleaningAllSuccess": "{{successCount}} metin bloğu için başarıyla veri temizleme tamamlandı", + "charsCount": "Karakter", + "pdfProcessStatus": "Toplam {{total}} dosya, {{completed}} dönüştürüldü", + "pdfPageProcessStatus": "{{fileName}} işleniyor, {{total}} sayfadan {{completed}} sayfa dönüştürüldü", + "pdfProcessing": "Dosya dönüştürülüyor...", + "pdfProcessingFailed": "Dosya dönüştürme başarısız!", + "pdfProcess": "Dosya algılandı!", + "selectPdfProcessingStrategy": "Lütfen dosya işleme yöntemini seçin:", + "pdfProcessingStrategyDefault": "Varsayılan", + "pdfProcessingStrategyDefaultHelper": "Yerleşik PDF ayrıştırma stratejisini kullan", + "pdfProcessingStrategyMinerUHelper": "Ayrıştırma için MinerU API kullan. Lütfen önce MinerU API Token yapılandırın", + "pdfProcessingStrategyVision": "Özel Görsel Model", + "pdfProcessingStrategyVisionHelper": "Ayrıştırma için özel görsel model kullan", + "pdfProcessingToast": "Dosya algılandı. Sistem, dosyayı ayrıştırmak için bir arka plan görevi oluşturacak", + "pdfProcessingWaring": "Devam eden bir dosya işleme görevi var. Görevin tamamlanmasını beklemeden diğer işlemleri yaparsanız veri üretim kalitesi etkilenebilir!", + "pdfProcessingLoading": "Dosya dönüştürme görevi çalıştırılıyor, lütfen görev tamamlanana kadar yeni dosya yüklemeyin...", + "basicPdfParsing": "Temel PDF Ayrıştırma", + "basicPdfParsingDesc": "Basit PDF dosyalarının temel ana hatlarını tanıyabilir, yüksek hız", + "mineruApiDesc": "Formüller ve grafikler dahil karmaşık PDF dosyalarını tanıyabilir (MinerU API Key yapılandırması gerektirir)", + "mineruLocalDesc": "Formüller ve grafikler dahil karmaşık PDF dosyalarını tanıyabilir (MinerU Local URL yapılandırması gerektirir)", + "mineruApiDescDisabled": "Lütfen Proje Ayarları - Görev Yapılandırması'na gidip MinerU token ayarlayın", + "mineruLocalDisabled": "Lütfen önce [Proje Ayarları - Görev Yapılandırması]'nda MinerU Local URL ayarlayın", + "mineruWebPlatform": "MinerU Çevrimiçi Platform Ayrıştırma", + "mineruWebPlatformDesc": "Formüller ve grafikler dahil karmaşık PDF dosyalarını tanıyabilir (Başka bir web sitesine yönlendirme gerektirir)", + "mineruSelected": "PDF'leri ayrıştırmak için MinerU kullanımı seçildi", + "mineruLocalSelected": "PDF'leri ayrıştırmak için MinerU Local kullanımı seçildi", + "customVisionModel": "Özel Görsel Model Ayrıştırma", + "customVisionModelDesc": "Formüller ve grafikler dahil karmaşık PDF dosyalarını tanıyabilir (Model yapılandırmasına görsel model yapılandırması eklenmesi gerekir)", + "customVisionModelSelected": "PDF'leri ayrıştırmak için {{name}} ({{provider}}) görsel büyük modeli kullanımı seçildi", + "defaultSelected": "PDF'leri ayrıştırmak için varsayılan yerleşik strateji kullanımı seçildi", + "download": "Belgeyi indir", + "deleteFile": "Belgeyi sil", + "viewChunk": "Metin Bloğunu Görüntüle", + "editChunk": "Metin Bloğunu Düzenle {{chunkId}}", + "editChunkSuccess": "Metin bloğu başarıyla düzenlendi", + "editChunkFailed": "Metin bloğu düzenlenemedi", + "editChunkError": "Metin bloğu düzenlenirken hata oluştu", + "deleteFileWarning": "Uyarı: Bu belgeyi silmek aşağıdaki ilgili öğeleri de silecektir", + "deleteFileWarningChunks": "İlişkili tüm metin blokları", + "deleteFileWarningQuestions": "Bu bloklardan üretilen tüm sorular", + "deleteFileWarningDatasets": "Bu sorulardan oluşturulan tüm veri setleri", + "domainTree": { + "firstUploadTitle": "Alan Ağacı Oluşturma", + "uploadTitle": "Belge Yükleme - Alan Ağacı İşleme", + "deleteTitle": "Belge Silme - Alan Ağacı İşleme", + "reviseOption": "Alan Ağacını Revize Et", + "reviseDesc": "Eklenen veya silinen belgelere göre mevcut alan ağacını değiştir, sadece değişen kısımları etkiler", + "rebuildOption": "Alan Ağacını Yeniden Oluştur", + "rebuildDesc": "Tüm belge içeriklerine göre tamamen yeni bir alan ağacı oluştur", + "keepOption": "Değiştirme", + "keepDesc": "Mevcut alan ağacı yapısını değiştirmeden koru" + } + }, + "domain": { + "title": "Alan Bilgi Ağacı", + "addRootTag": "Kök Etiket Ekle", + "addFirstTag": "İlk Etiket Ekle", + "noTags": "Alan etiketleri mevcut değil", + "docStructure": "Belge Yapısı", + "noToc": "İçindekiler tablosu mevcut değil. Lütfen önce belgeyi yükleyin ve işleyin.", + "editTag": "Etiketi Düzenle", + "deleteTag": "Etiketi Sil", + "addChildTag": "Alt Etiket Ekle", + "deleteTagConfirmTitle": "Etiketi Sil", + "deleteTagConfirmMessage": "\"{{tag}}\" etiketini silmek istediğinizden emin misiniz?", + "deleteWarning": "Bu işlem bu etiketi ve tüm alt etiketlerini, sorularını ve veri setlerini silecektir. Bu işlem geri alınamaz!", + "dialog": { + "addTitle": "Etiket Ekle", + "editTitle": "Etiketi Düzenle", + "addChildTitle": "Alt Etiket Ekle", + "inputRoot": "Lütfen yeni bir kök etiket adı girin", + "inputEdit": "Lütfen etiket adını düzenleyin", + "inputChild": "Lütfen \"{label}\" için bir alt etiket ekleyin", + "labelName": "Etiket Adı", + "saving": "Kaydediliyor...", + "save": "Kaydet", + "deleteConfirm": "\"{label}\" etiketini silmek istediğinizden emin misiniz?", + "deleteWarning": "Bu işlem tüm alt etiketleri de silecektir ve geri alınamaz.", + "emptyLabel": "Etiket adı boş olamaz" + }, + "tabs": { + "tree": "Alan Ağacı", + "structure": "Belge Yapısı" + }, + "errors": { + "saveFailed": "Etiketler kaydedilemedi" + }, + "messages": { + "updateSuccess": "Etiketler başarıyla güncellendi" + } + }, + "export": { + "alpacaSettings": "Alpaca Format Ayarları", + "questionFieldType": "Soru Alanı Türü", + "useInstruction": "instruction alanını kullan", + "useInput": "input alanını kullan", + "customInstruction": "Özel Instruction İçeriği", + "instructionPlaceholder": "Sabit instruction içeriği girin", + "instructionHelperText": "Input alanı kullanılırken burada sabit instruction içeriği belirtebilirsiniz", + "title": "Dışa Aktar", + "format": "Format", + "fileFormat": "Dosya Formatı", + "systemPrompt": "Sistem İstemi", + "systemPromptPlaceholder": "Lütfen sistem istemi girin...", + "ReasoninglanguagePlaceholder": "Lütfen Akıl Yürütme dilini girin: İngilizce veya Çince veya diğerleri", + "onlyConfirmed": "Sadece onaylanmış verileri dışa aktar", + "example": "Format Örneği", + "confirmExport": "Dışa Aktarmayı Onayla", + "includeCOT": "Düşünce Zincirini Dahil Et", + "cotDescription": "Nihai cevaptan önceki akıl yürütme sürecini içerir", + "customFormat": "Özel Format", + "customFormatSettings": "Özel Format Ayarları", + "questionFieldName": "Soru Alanı Adı", + "multilingualThinkingFormat": "Multilingual‑Thinking", + "Reasoninglanguage": "Akıl Yürütme dili", + "sampleInstruction": "İnsan talimatı (gerekli)", + "sampleOutput": "Model yanıtı (gerekli)", + "sampleSystem": "Sistem istemi (isteğe bağlı)", + "sampleInstruction2": "İkinci talimat", + "sampleOutput2": "İkinci yanıt", + "sampleSystemShort": "Sistem istemi", + "fixedInstruction": "Sabit talimat içeriği", + "sampleInput": "İnsan sorusu (gerekli)", + "sampleInput2": "İkinci soru", + "sampleInputOptional": "İnsan girişi (isteğe bağlı)", + "sampleUserMessage": "İnsan talimatı", + "sampleAssistantMessage": "Model yanıtı", + "sampleAnalysis": "Modelin düşünce zinciri içeriği", + "sampleFinal": "Model yanıtı", + "sampleThinking": "Modelin düşünce zinciri içeriği", + "fetchLabelStatsError": "Etiket istatistikleri getirilemedi:" + }, + "import": { + "title": "İçe Aktar", + "fileUpload": "Dosya Yükleme", + "fileUploadDescription": "Veri setlerini içe aktarmak için yerel dosyaları yükleyin", + "mapFields": "Alan Eşleme", + "importing": "İçe Aktarılıyor", + "uploadFile": "Dosya Yükle", + "supportedFormats": "JSON, JSONL, CSV format dosyalarını destekler", + "dragDropFile": "Dosyaları buraya sürükleyin veya dosya seçmek için tıklayın", + "dropFileHere": "Dosyayı yüklemek için bırakın", + "maxFileSize": "Maksimum dosya boyutu: 50MB", + "processingFile": "Dosya işleniyor...", + "uploadedFiles": "Yüklenen Dosyalar", + "uploadError": "Dosya yükleme başarısız, lütfen dosya formatını kontrol edin", + "selectFromSource": "{{source}} kaynağından veri seti seç", + "sourceDescription": "Aramak için veri seti adı veya anahtar kelimeler girin", + "datasetName": "Veri Seti Adı", + "hfPlaceholder": "örn.: squad, glue, imdb", + "msPlaceholder": "örn.: damo/nlp_bert_document-classification", + "search": "Ara", + "searching": "Aranıyor", + "searchResults": "Arama Sonuçları", + "downloads": "İndirmeler", + "download": "İndir", + "downloading": "İndiriliyor", + "hfNote": "Not: Büyük veri setlerini indirmek uzun sürebilir, test için daha küçük veri setleri seçmeniz önerilir.", + "msNote": "Not: ModelScope veri seti indirmesi ağ bağlantısı gerektirir, lütfen ağ bağlantınızı kontrol edin.", + "fieldMapping": "Alan Eşleme", + "mappingDescription": "Lütfen kaynak veri alanlarını hedef alanlara eşleyin. Sistem olası eşleme ilişkilerini otomatik olarak belirledi, gerektiğinde ayarlayabilirsiniz.", + "selectMapping": "Alan Eşlemesini Seç", + "questionField": "Soru Alanı", + "answerField": "Cevap Alanı", + "cotField": "Düşünce Zinciri Alanı", + "tagsField": "Etiketler Alanı", + "selectField": "Alan Seç", + "questionDesc": "Kullanıcının sorusu veya giriş içeriği (gerekli)", + "answerDesc": "AI'nın cevabı veya çıktı içeriği (gerekli)", + "cotDesc": "Düşünce zinciri veya akıl yürütme süreci (isteğe bağlı)", + "tagsDesc": "Etiket dizisi, virgülle ayrılmış birden çok etiket (isteğe bağlı)", + "dataPreview": "Veri Önizlemesi", + "previewNote": "İlk 3 kaydı gösterir, her alan değeri en fazla 100 karakter gösterir", + "confirmMapping": "Eşlemeyi Onayla", + "requiredFields": "Lütfen en az soru ve cevap alanı eşlemelerini seçin", + "mappingRequired": "Soru ve cevap alanları gereklidir", + "duplicateMapping": "Birden çok hedef alanı aynı kaynak alanına eşleyemezsiniz", + "noPreviewData": "Önizleme verisi yok", + "preparingData": "Veri hazırlanıyor...", + "uploadingData": "Veri yükleniyor...", + "processing": "İşleniyor... {{processed}}/{{total}}", + "completed": "İçe aktarma tamamlandı", + "importStats": "İçe Aktarma İstatistikleri", + "total": "Toplam: {{count}}", + "success": "Başarılı: {{count}}", + "failed": "Başarısız: {{count}}", + "source": "Veri Kaynağı", + "description": "Açıklama", + "errors": "Hata Mesajları", + "moreErrors": "{{count}} hata daha gösterilmiyor...", + "importSuccess": "Veri seti içe aktarma tamamlandı!", + "enterDatasetName": "Lütfen veri seti adını girin", + "noDatasetFound": "Eşleşen veri seti bulunamadı", + "complete": "Tamamla" + }, + "export_extended": { + "answerFieldName": "Cevap Alanı Adı", + "cotFieldName": "Cot Alanı Adı", + "includeLabels": "Etiketleri Dahil Et", + "includeChunk": "Metin Bloğunu Dahil Et", + "questionOnly": "Sadece Soruları Dışa Aktar", + "localTab": "Yerel Dışa Aktarma", + "llamaFactoryTab": "Llama Factory", + "huggingFaceTab": "HuggingFace", + "configExists": "Yapılandırma Dosyası Mevcut", + "configPath": "Yapılandırma Dosyası Yolu", + "updateConfig": "LLaMA Factory Yapılandırmasını Güncelle", + "noConfig": "Yapılandırma dosyası mevcut değil, oluşturmak için aşağıdaki düğmeye tıklayın", + "generateConfig": "LLaMA Factory Yapılandırması Oluştur", + "huggingFaceComingSoon": "HuggingFace dışa aktarma özelliği yakında", + "uploadToHuggingFace": "HuggingFace'e Yükle", + "datasetName": "Veri Seti Adı", + "datasetNameHelp": "Format: kullanıcıadı/veri-seti-adı", + "privateDataset": "Özel Veri Seti", + "datasetSettings": "Veri Seti Ayarları", + "exportOptions": "Dışa Aktarma Seçenekleri", + "uploadSuccess": "Veri seti HuggingFace'e başarıyla yüklendi", + "viewOnHuggingFace": "HuggingFace'de Görüntüle", + "noTokenWarning": "Hugging Face Token bulunamadı. Lütfen proje ayarlarında yapılandırın.", + "goToSettings": "Ayarlara Git", + "tokenHelp": "Token'ınızı HuggingFace ayarlar sayfasından alabilirsiniz" + }, + "datasets": { + "loadingDataset": "Veri Seti Yükleniyor...", + "datasetNotFound": "Veri Seti Bulunamadı", + "optimizeTitle": "AI Optimize Et", + "optimizeAdvice": "Optimizasyon Önerisi", + "optimizePlaceholder": "Lütfen cevabı iyileştirmek için önerilerinizi girin, AI önerilerinize göre cevabı ve akıl yürütme zincirini optimize edecektir", + "generatingDataset": "Veri Seti Oluşturuluyor", + "aiOptimizeAdvicePlaceholder": "Lütfen cevabı iyileştirmek için önerilerinizi girin, AI önerilerinize göre cevabı ve akıl yürütme zincirini optimize edecektir", + "aiOptimizeAdvice": "Lütfen cevabı iyileştirmek için önerilerinizi girin, AI önerilerinize göre cevabı ve akıl yürütme zincirini optimize edecektir", + "aiOptimize": "AI Optimize Et", + "partialSuccess": "Kısmi başarılı veri seti üretimi ({{successCount}}/{{total}}), {{failCount}} soru başarısız", + "generating": "Veri Seti Oluşturuluyor", + "generateError": "Veri seti oluşturulamadı", + "management": "Veri Setleri", + "question": "Soru", + "filterAll": "Tümü", + "filterConfirmed": "Onaylandı", + "filterUnconfirmed": "Onaylanmadı", + "createdAt": "Oluşturma Tarihi", + "model": "Model", + "domainTag": "Alan Etiketi", + "cot": "COT", + "answer": "Cevap", + "chunkId": "Metin Bloğu", + "confirmed": "Onaylandı", + "noTag": "Etiket Yok", + "noData": "Veri Yok", + "rowsPerPage": "Sayfa başına satır", + "pagination": "{{from}}-{{to}} / {{count}}", + "confirmDeleteMessage": "Bu veri setini silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "questionLabel": "Soru", + "fetchFailed": "Veri seti getirilemedi", + "deleteFailed": "Veri seti silinemedi", + "deleteSuccess": "Başarıyla silindi", + "exportSuccess": "Veri seti başarıyla dışa aktarıldı", + "exportFailed": "Dışa aktarma başarısız", + "exportProgress": "Dışa Aktarma İlerlemesi", + "exportingData": "Veri seti dışa aktarılıyor", + "processedCount": "İşlenen {{processed}} / {{total}} öğe", + "exportInProgress": "Veri getiriliyor, lütfen bekleyin...", + "exportFinalizing": "Dosya oluşturuluyor, neredeyse bitti...", + "loading": "Veri setleri yükleniyor...", + "stats": "Toplam {{total}} veri seti, {{confirmed}} onaylandı ({{percentage}}%)", + "selected": "Toplam seçilen: {{count}}", + "batchconfirmDeleteMessage": "Seçilen {{count}} soruyu silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "batchDelete": "Toplu Sil", + "batchDeleteProgress": "Tamamlanan: {{completed}}/{{total}}", + "batchDeleteCount": "Silme sayısı: {{count}}", + "searchPlaceholder": "Veri setlerinde ara...", + "fieldQuestion": "Soru", + "fieldAnswer": "Cevap", + "fieldCOT": "COT", + "fieldLabel": "Alan Etiketi", + "moreFilters": "Daha Fazla Filtre", + "filtersTitle": "Filtre Seçenekleri", + "filterConfirmationStatus": "Onay Durumu", + "filterCotStatus": "Düşünce Zinciri Durumu", + "filterHasCot": "CoT Var", + "filterNoCot": "CoT Yok", + "filterScoreRange": "Puan Aralığı", + "filterNoteKeyword": "Not Anahtar Kelimesi", + "filterNoteKeywordPlaceholder": "Not anahtar kelimesi girin...", + "filterChunkName": "Blok Adı", + "filterChunkNamePlaceholder": "Blok adı girin...", + "filterCustomTag": "Özel Etiket", + "resetFilters": "Sıfırla", + "applyFilters": "Uygula", + "viewDetails": "Detayları Görüntüle", + "datasetDetail": "Veri Seti Detayları", + "metadata": "Meta Veri", + "confirmSave": "Kaydetmeyi Onayla", + "unconfirm": "Onayı Kaldır", + "unconfirming": "Onay kaldırılıyor...", + "uncategorized": "Kategorisiz", + "questionCount": "{{count}} Soru", + "source": "Kaynak", + "generateDataset": "Veri Seti Oluştur", + "generateNotImplemented": "Veri seti oluşturma uygulanmadı", + "generateSuccess": "Başarıyla veri seti oluşturuldu: {{question}}", + "generateFailed": "Veri seti oluşturulamadı: {{error}}", + "noTagsAndQuestions": "Etiket ve soru yok", + "answerCount": "{{count}} Cevap", + "answered": "Cevaplandı", + "enableShortcuts": "Sayfa kısayol tuşu", + "shortcutsHelp": "← ileri, → geri, y onaylamak, d silmek için basın", + "filterDistill": "Damıtılmış Veri Seti", + "filterDistillYes": "Damıtılmış Veri Seti", + "filterDistillNo": "Damıtılmamış Veri Seti", + "evaluation": "Veri Seti Değerlendirmesi", + "rating": "Puan", + "ratingExcellent": "Mükemmel", + "ratingGood": "İyi", + "ratingAverage": "Orta", + "ratingPoor": "Zayıf", + "ratingVeryPoor": "Çok Zayıf", + "ratingUnrated": "Puanlanmamış", + "customTags": "Özel Etiketler", + "addCustomTag": "Özel etiket ekle...", + "note": "Not", + "addNote": "Not ekle...", + "noNote": "Not yok", + "clickToAddNote": "Not eklemek için tıklayın...", + "enterNote": "Not girin...", + "noteShortcuts": "Kaydetmek için Ctrl+Enter, iptal için Esc", + "aiEvaluation": "AI Kalite Değerlendirmesi", + "addToEval": "Değerlendirme Veri Setine Ekle", + "addToEvalSuccess": "Değerlendirme veri setine başarıyla eklendi", + "addToEvalFailed": "Ekleme başarısız", + "generateEvalVariant": "Değerlendirme Varyantı Oluştur", + "generateVariantFailed": "Varyant oluşturulamadı", + "saveVariantSuccess": "Değerlendirme veri setine kaydedildi", + "saveVariantFailed": "Kaydetme başarısız", + "evalVariantTitle": "Değerlendirme Varyantı Oluştur", + "evalVariantPreviewTitle": "Oluşturulan Soruları Onayla", + "saveToEval": "Değerlendirme Veri Setine Kaydet", + "evalVariantConfigHint": "Lütfen soru türünü ve sayısını seçin. AI mevcut S&C çiftine göre yeniden yazacaktır.", + "questionType": "Soru Türü", + "typeOpenEnded": "Açık uçlu", + "typeSingleChoice": "Tek Seçimli", + "typeMultipleChoice": "Çok Seçimli", + "typeTrueFalse": "Doğru/Yanlış", + "typeShortAnswer": "Kısa Cevap", + "generateCount": "Oluşturma Sayısı", + "evalVariantPreviewHint": "Oluşturulan soruları düzenleyebilir ve doğruladıktan sonra değerlendirme setine kaydedebilirsiniz.", + "questionIndex": "Soru {{index}}", + "options": "Seçenekler (JSON Dizisi)", + "optionsHint": "Örn.: [\"Seçenek A\", \"Seçenek B\"]", + "answerArrayHint": "Çok seçimli için lütfen bir dizi girin, örn. [\"A\", \"C\"]", + "answerBoolHint": "Doğru/Yanlış için lütfen ✅ veya ❌ girin", + "generate": "Oluştur", + "updateSuccess": "Güncelleme başarılı", + "updateFailed": "Güncelleme başarısız", + "evaluate": "Değerlendir", + "evaluating": "Değerlendiriliyor...", + "batchEvaluate": "Toplu Değerlendir", + "selectModelFirst": "Lütfen önce bir model seçin", + "evaluateSuccess": "Değerlendirme tamamlandı! Puan: {{score}}/5", + "evaluateFailed": "Değerlendirme başarısız", + "evaluateError": "Değerlendirme başarısız: {{error}}", + "batchEvaluateStarted": "Toplu değerlendirme görevi başlatıldı, arka planda işleniyor", + "batchEvaluateStartFailed": "Toplu değerlendirme başlatılamadı", + "batchEvaluateFailed": "Toplu değerlendirme başarısız: {{error}}", + "scoreRange": "{{min}} - {{max}} puan", + "singleTurn": "Tek Turlu S&C Veri Seti", + "multiTurn": "Çok Turlu Konuşma Veri Seti", + "imageQA": "Görsel S&C Veri Seti", + "conversationDetail": "Çok Turlu Konuşma Detayları", + "conversationContent": "Konuşma İçeriği", + "basicInfo": "Temel Bilgiler", + "firstQuestion": "İlk Soru", + "conversationScenario": "Konuşma Senaryosu", + "conversationRounds": "Konuşma Turları", + "modelUsed": "Kullanılan Model", + "qualityScore": "Kalite Puanı", + "notes": "Notlar", + "createTime": "Oluşturma Zamanı", + "notSet": "Ayarlanmadı", + "noTags": "Etiket Yok", + "noNotes": "Not Yok", + "notEvaluated": "Değerlendirilmedi", + "round": "Tur {{round}}", + "system": "Sistem", + "user": "Kullanıcı", + "assistant": "Asistan", + "confirmDelete": "Silmeyi Onayla", + "confirmDeleteConversation": "Bu çok turlu konuşma veri setini silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "conversationNotFound": "Konuşma veri seti bulunamadı", + "fetchDataFailed": "Veri getirilemedi", + "saveFailed": "Kaydetme başarısız", + "saveSuccess": "Başarıyla kaydedildi", + "saving": "Kaydediliyor...", + "inputTagsPlaceholder": "Etiketleri girin, boşlukla ayırın", + "addNotesPlaceholder": "Not ekle", + "noConversations": "Çok turlu konuşma yok", + "notRated": "Puanlanmadı", + "minScore": "Min Puan", + "maxScore": "Max Puan", + "unconfirmed": "Onaylanmadı" + }, + "rating": { + "veryPoor": "Çok Zayıf", + "poor": "Zayıf", + "belowAverage": "Ortalamanın Altında", + "fair": "Vasat", + "average": "Orta", + "good": "İyi", + "veryGood": "Çok İyi", + "excellent": "Mükemmel", + "outstanding": "Üstün", + "perfect": "Kusursuz", + "unrated": "Puanlanmamış" + }, + "tags": { + "noTags": "Etiket yok", + "addTag": "Etiket ekle...", + "addCustomTag": "Özel etiket ekle", + "maxTagsReached": "Maksimum {{maxTags}} etikete ulaşıldı", + "availableTagsHint": "Mevcut etiketlerden seçin veya yeni etiket girin" + }, + "update": { + "newVersion": "Yeni Sürüm", + "newVersionAvailable": "Yeni Sürüm Mevcut", + "currentVersion": "Mevcut Sürüm", + "latestVersion": "En Son Sürüm", + "downloadNow": "Şimdi İndir", + "downloading": "İndiriliyor", + "installNow": "Şimdi Yükle", + "updating": "Güncelleniyor...", + "updateNow": "Şimdi Güncelle", + "viewRelease": "Sürüm Notlarını Görüntüle", + "checking": "Güncellemeler kontrol ediliyor...", + "noUpdates": "Zaten güncel", + "updateError": "Güncelleme Hatası", + "updateSuccess": "Güncelleme Başarılı", + "restartRequired": "Yeniden Başlatma Gerekli", + "restartNow": "Şimdi Yeniden Başlat", + "restartLater": "Daha Sonra Yeniden Başlat" + }, + "datasetSquare": { + "title": "Veri Seti Meydanı", + "subtitle": "Model eğitimi ve araştırmanızı desteklemek için çeşitli genel veri seti kaynaklarını keşfedin", + "searchPlaceholder": "Veri seti anahtar kelimeleri ara...", + "searchVia": "Arama yolu", + "categoryTitle": "Veri Seti Kategorileri", + "categories": { + "all": "Tümü", + "popular": "Popüler", + "chinese": "Çince Kaynaklar", + "english": "İngilizce Kaynaklar", + "research": "Araştırma Verileri", + "multimodal": "Çok Modlu" + }, + "foundResources": "{{count}} veri seti kaynağı bulundu", + "currentFilter": "Mevcut filtre: {{category}}", + "noDatasets": "Kriterlerinize uygun veri seti bulunamadı", + "tryOtherCategories": "Lütfen diğer kategorileri deneyin veya tüm veri setlerini görüntülemek için geri dönün", + "dataset": "Veri Seti", + "viewDataset": "Veri Setini Görüntüle" + }, + "playground": { + "title": "Model Testi", + "selectModelFirst": "Lütfen bir model seçin", + "sendFirstMessage": "Test başlatmak için ilk mesajınızı gönderin", + "inputMessage": "Mesaj girin...", + "send": "Gönder", + "outputMode": "Çıktı Modu", + "normalOutput": "Normal Çıktı", + "streamingOutput": "Akış Çıktısı", + "clearConversation": "Konuşmayı Temizle", + "selectModelMax3": "Test için en fazla 3 model seçin", + "reasoningProcess": "Akıl Yürütme Zinciri" + }, + "chunks": { + "title": "Metin Bloğu", + "defaultTitle": "Varsayılan Başlık" + }, + "documentation": "Dokümantasyon", + "models": { + "configNotFound": "Model yapılandırması bulunamadı", + "parseError": "Model yapılandırması ayrıştırılamadı", + "fetchFailed": "Model getirilemedi", + "saveFailed": "Model yapılandırması kaydedilemedi", + "pleaseSelectModel": "Lütfen en az bir model seçin", + "title": "Model Ayarları", + "add": "Model Ekle", + "unselectedModel": "Seçilmemiş Model", + "unconfiguredAPIKey": "Yapılandırılmamış API Anahtarı", + "saveAllModels": "Tüm Modelleri Kaydet", + "edit": "Düzenle", + "delete": "Sil", + "modelName": "Model Adı", + "endpoint": "Uç Nokta", + "apiKey": "API Anahtarı", + "provider": "Sağlayıcı", + "localModel": "Yerel Model", + "apiKeyConfigured": "API Anahtarı Yapılandırıldı", + "apiKeyNotConfigured": "API Anahtarı Yapılandırılmadı", + "temperature": "Sıcaklık", + "maxTokens": "Maks Token", + "maxTokensInputTip": "Kaydırıcı aralığı: 1-{{max}}. Ayrıca herhangi bir pozitif tam sayı girebilirsiniz.", + "topP": "Top P", + "type": "Model Türü", + "text": "Büyük Dil Modeli", + "vision": "Görsel Büyük Model", + "typeTips": "PDF'leri ayrıştırmak için özel görsel model kullanmak istiyorsanız, lütfen en az bir görsel büyük model yapılandırın", + "refresh": "Modelleri Yenile", + "configuredModels": "Yapılandırılmış Modeller", + "unconfiguredModels": "Yapılandırılmamış Modeller", + "noConfiguredModels": "Yapılandırılmış model yok", + "noUnconfiguredModels": "Yapılandırılmamış model yok", + "checkEndpointHealth": "Uç nokta sağlığını kontrol et", + "checkAllEndpointHealth": "Tüm uç noktaları kontrol et", + "endpointHealthy": "Uç nokta sağlıklı", + "endpointCheckFailed": "Uç nokta kontrolü başarısız", + "endpointMissing": "Uç nokta boş", + "endpointReachableModelMissing": "Uç nokta erişilebilir, ancak mevcut model dönen listede yok", + "healthCheckSummary": "Sağlık kontrolü tamamlandı: {{okCount}} sağlıklı, {{failCount}} başarısız", + "checking": "Kontrol ediliyor...", + "healthy": "Sağlıklı", + "reachable": "Erişilebilir", + "unhealthy": "Sağlıksız", + "notChecked": "Kontrol edilmedi" + }, + "stats": { + "ongoingProjects": "Devam Eden Projeler", + "questionCount": "Soru Sayısı", + "generatedDatasets": "Oluşturulan Veri Setleri", + "supportedModels": "Desteklenen Modeller" + }, + "migration": { + "title": "Proje Taşıma", + "description": "Bazı projelerin veritabanına taşınması gerekiyor. Taşıma, performansı artırabilir ve daha fazla özelliği destekleyebilir.", + "projectsList": "Taşınmamış Projeler", + "migrate": "Taşımayı Başlat", + "migrating": "Taşınıyor...", + "success": "{{count}} proje başarıyla taşındı", + "failed": "Taşıma başarısız", + "checkFailed": "Taşınmamış projeler kontrol edilemedi", + "checkError": "Taşınmamış projeler kontrol edilirken hata", + "starting": "Taşıma görevi başlatılıyor...", + "processing": "Taşıma görevi işleniyor...", + "completed": "Taşıma tamamlandı", + "startFailed": "Taşıma görevi başlatılamadı", + "statusFailed": "Taşıma durumu alınamadı", + "taskNotFound": "Taşıma görevi bulunamadı", + "progressStatus": "{{completed}}/{{total}} proje taşındı", + "openDirectory": "Dizini Aç", + "deleteDirectory": "Dizini Sil", + "confirmDelete": "Bu proje dizinini silmek istediğinizden emin misiniz? Bu işlem geri alınamaz.", + "openDirectoryFailed": "Proje dizini açılamadı", + "deleteDirectoryFailed": "Proje dizini silinemedi" + }, + "distill": { + "title": "Damıt", + "generateRootTags": "Kök Etiketler Oluştur", + "generateSubTags": "Alt Etiketler Oluştur", + "generateQuestions": "Soru Oluştur", + "generateRootTagsTitle": "Kök Alan Etiketleri Oluştur", + "generateSubTagsTitle": "{{parentTag}} için Alt Etiketler Oluştur", + "generateQuestionsTitle": "{{tag}} için Sorular Oluştur", + "parentTag": "Üst Etiket", + "parentTagPlaceholder": "Üst etiket adını girin (örn., Spor, Teknoloji)", + "parentTagHelp": "Bir alan konusu girin, sistem buna göre ilgili etiketler oluşturacaktır", + "generateQuestionsError": "Sorular oluşturulamadı", + "tagCount": "Etiket Sayısı", + "tagCountHelp": "Oluşturulacak etiket sayısını girin, maksimum 100", + "questionCount": "Soru Sayısı", + "questionCountHelp": "Oluşturulacak soru sayısını girin, maksimum 100", + "generatedTags": "Oluşturulan Etiketler", + "generatedQuestions": "Oluşturulan Sorular", + "tagPath": "Etiket Yolu", + "noTags": "Etiket Yok", + "noQuestions": "Soru Yok", + "clickGenerateButton": "Etiket oluşturmak için yukarıdaki oluştur butonuna tıklayın", + "selectModelFirst": "Lütfen önce bir model seçin", + "selectModel": "Model Seç", + "generateTagsError": "Etiketler oluşturulamadı", + "generateTags": "Etiket Oluştur", + "subTags": "alt-etiket", + "questions": "soru", + "deleteTagConfirmTitle": "Etiketi silmeyi onayla?", + "editTagTitle": "Etiketi Düzenle", + "tagName": "Etiket Adı", + "labelRequired": "Etiket adı boş olamaz", + "tagUpdateSuccess": "Etiket başarıyla güncellendi", + "tagUpdateFailed": "Etiket güncellenemedi", + "unknownTag": "Bilinmeyen Etiket", + "autoDistillButton": "Otomatik Damıtma Veri Seti", + "autoDistillTitle": "Otomatik Veri Seti Damıtma Yapılandırması", + "distillTopic": "Damıtma Konusu", + "tagLevels": "Etiket Seviyeleri", + "tagLevelsHelper": "Seviye sayısını ayarlayın, maksimum {{max}}", + "tagsPerLevel": "Seviye Başına Etiket", + "tagsPerLevelHelper": "Her üst etiket altında oluşturulacak alt etiket sayısı, maksimum {{max}}", + "questionsPerTag": "Etiket Başına Soru", + "questionsPerTagHelper": "Her yaprak etiket için oluşturulacak soru sayısı, maksimum {{max}}", + "estimationInfo": "Görev Tahmin Bilgisi", + "estimatedTags": "Tahmini Etiketler", + "estimatedQuestions": "Tahmini Sorular", + "currentTags": "Mevcut Etiketler", + "currentQuestions": "Mevcut Sorular", + "newTags": "Yeni Etiketler", + "newQuestions": "Yeni Sorular", + "startAutoDistill": "Otomatik Damıtmayı Başlat", + "autoDistillProgress": "Otomatik Damıtma İlerlemesi", + "overallProgress": "Genel İlerleme", + "tagsProgress": "Etiket Oluşturma İlerlemesi", + "questionsProgress": "Soru Üretme İlerlemesi", + "currentStage": "Mevcut Aşama", + "realTimeLogs": "Gerçek Zamanlı Günlükler", + "waitingForLogs": "Günlükler bekleniyor...", + "autoDistillStarted": "{{time}} Otomatik damıtma görevi başladı", + "autoDistillInsufficientError": "Mevcut yapılandırma yeni etiket veya soru üretmeyecek, lütfen parametreleri ayarlayın", + "stageInitializing": "Başlatılıyor...", + "stageBuildingLevel1": "Seviye 1 Etiketleri Oluşturuluyor", + "stageBuildingLevel2": "Seviye 2 Etiketleri Oluşturuluyor", + "stageBuildingLevel3": "Seviye 3 Etiketleri Oluşturuluyor", + "stageBuildingLevel4": "Seviye 4 Etiketleri Oluşturuluyor", + "stageBuildingLevel5": "Seviye 5 Etiketleri Oluşturuluyor", + "stageBuildingQuestions": "Sorular Oluşturuluyor", + "stageBuildingDatasets": "Veri Setleri Oluşturuluyor", + "stageCompleted": "Görev Tamamlandı", + "datasetsProgress": "Veri Setleri İlerlemesi", + "rootTopicHelperText": "Varsayılan olarak proje adı üst düzey damıtma konusu olarak kullanılır. Değiştirmek isterseniz, lütfen proje ayarlarına giderek proje adını değiştirin.", + "addChildTag": "Alt Etiket Ekle", + "datasetType": "Veri Seti Türü", + "singleTurnDataset": "Tek Turlu Veri Seti", + "multiTurnDataset": "Çok Turlu Veri Seti", + "bothDatasetTypes": "Her İki Veri Seti Türünü Oluştur", + "autoDistillTaskDetail": "Otomatik Damıtma Görevi: {{topic}}", + "backgroundTaskCreated": "Arka plan damıtma görevi oluşturuldu. İlerlemeyi görev yönetiminde kontrol edebilirsiniz.", + "backgroundTaskFailed": "Arka plan görevi oluşturulamadı", + "taskExecutionError": "Görev yürütme hatası: {{error}}" + }, + "tasks": { + "pending": "{{count}} görev işleniyor", + "completed": "görevler tamamlandı", + "title": "Görev Yönetim Merkezi", + "loading": "Görevler yükleniyor...", + "empty": "Görev bulunamadı", + "confirmDelete": "Bu görevi silmek istediğinizden emin misiniz?", + "confirmAbort": "Bu görevi iptal etmek istediğinizden emin misiniz? Görev durdurulacaktır.", + "deleteSuccess": "Görev silindi", + "deleteFailed": "Görev silinemedi", + "abortSuccess": "Görev iptal edildi", + "abortFailed": "Görev iptal edilemedi", + "status": { + "processing": "İşleniyor", + "completed": "Tamamlandı", + "failed": "Başarısız", + "aborted": "İptal Edildi", + "unknown": "Bilinmiyor" + }, + "types": { + "text-processing": "Metin İşleme", + "question-generation": "Soru Üretimi", + "answer-generation": "Cevap Üretimi", + "data-distillation": "Veri Damıtma", + "pdf-processing": "PDF İşleme" + }, + "filters": { + "status": "Görev Durumu", + "type": "Görev Türü" + }, + "actions": { + "refresh": "Görev listesini yenile", + "delete": "Görevi sil", + "abort": "Görevi iptal et" + }, + "table": { + "type": "Tür", + "status": "Durum", + "progress": "İlerleme", + "success": "Başarılı", + "failed": "Başarısız", + "createTime": "Oluşturuldu", + "endTime": "Tamamlandı", + "duration": "Süre", + "model": "Model", + "detail": "Detaylar", + "actions": "İşlemler" + }, + "duration": { + "seconds": "{{seconds}}s", + "minutes": "{{minutes}}d {{seconds}}s", + "hours": "{{hours}}s {{minutes}}d" + }, + "fetchFailed": "Görev listesi getirilemedi", + "createSuccess": "Görev başarıyla oluşturuldu", + "createFailed": "Görev oluşturulamadı", + "multiTurnCreateSuccess": "Çok turlu konuşma veri seti görevi başarıyla oluşturuldu" + }, + "gaPairs": { + "title": "Tür-Kitle Çiftleri Yönetimi", + "loading": "GA çiftleri yükleniyor...", + "addPair": "GA Çifti Ekle", + "saveChanges": "Değişiklikleri Kaydet", + "saving": "Kaydediliyor...", + "restoreBackup": "Yedeği Geri Yükle", + "noGaPairsTitle": "Tür-Kitle Çifti Bulunamadı", + "noGaPairsDescription": "Bu dosya için AI destekli Tür-Kitle çiftleri oluştur", + "generateGaPairs": "Tür-Kitle Çiftleri Oluştur", + "generating": "Oluşturuluyor...", + "generateMore": "Daha Fazla Tür-Kitle Çifti Oluştur", + "activePairs": "Aktif Tür-Kitle Çiftleri ({{active}}/{{total}})", + "pairNumber": "Tür-Kitle Çifti #{{number}}", + "active": "Aktif", + "deleteTooltip": "GA Çiftini Sil", + "genre": "Tür", + "genreDescription": "Tür Açıklaması", + "audience": "Kitle", + "audienceDescription": "Kitle Açıklaması", + "addDialogTitle": "Yeni Tür-Kitle Çifti Ekle", + "genreTitle": "Tür Başlığı", + "audienceTitle": "Kitle Başlığı", + "genreTitlePlaceholder": "Tür başlığını girin...", + "genreDescPlaceholder": "Türü detaylı olarak açıklayın...", + "audienceTitlePlaceholder": "Kitle başlığını girin...", + "audienceDescPlaceholder": "Hedef kitleyi detaylı olarak açıklayın...", + "cancel": "İptal", + "addPairButton": "Tür-Kitle Çifti Ekle", + "requiredFields": "Tür Başlığı ve Kitle Başlığı gereklidir", + "restoredFromBackup": "Yedekten geri yüklendi", + "allPairsDeleted": "Tüm GA çiftleri başarıyla silindi", + "pairsSaved": "{{count}} GA çifti başarıyla kaydedildi", + "additionalPairsGenerated": "{{count}} ek Tür-Kitle çifti başarıyla oluşturuldu. Toplam: {{total}}", + "validationError": "GA çifti {{number}}: Tür ve Kitle başlıkları gereklidir", + "loadError": "GA çiftleri yüklenemedi: {{error}}", + "generateError": "GA çiftleri oluşturulamadı", + "saveError": "GA çiftleri kaydedilemedi", + "noActiveModel": "GA çiftleri oluşturmadan önce ayarlarda bir AI modeli yapılandırın.", + "contentTooShort": "Dosya içeriği çok kısa veya GA çifti oluşturma için uygun değil.", + "configError": "AI model yapılandırma hatası. Gerekli bağımlılıklar yüklenmemiş olabilir.", + "serverError": "Sunucu hatası ({{status}}). Lütfen daha sonra tekrar deneyin.", + "emptyResponse": "Oluşturma servisinden boş yanıt", + "generationFailed": "Oluşturma başarısız", + "saveOperationFailed": "Kaydetme işlemi başarısız", + "serviceNotAvailable": "GA Çiftleri oluşturma servisi kullanılamıyor. Lütfen API yapılandırmanızı kontrol edin.", + "requestFailed": "İstek başarısız ({{status}}). Lütfen tekrar deneyin.", + "internalServerError": "İç sunucu hatası oluştu.", + "batchGenerate": "Toplu GA Çifti Oluştur", + "batchGenerateDescription": "Seçilen {{count}} dosya için toplu GA çiftleri oluşturulacaktır. Bu işlem biraz zaman alabilir.", + "appendMode": "Ekleme Modu", + "appendModeDescription": "Zaten GA çiftleri olan dosyalar için üzerine yazmak yerine ek GA çiftleri oluştur", + "selectAtLeastOneFile": "Lütfen önce en az bir dosya seçin", + "noDefaultModel": "Varsayılan model ayarlanmamış, lütfen önce proje ayarlarında bir model yapılandırın", + "incompleteModelConfig": "Model yapılandırması eksik, lütfen model ayarlarını kontrol edin", + "missingApiKey": "Model API anahtarı yapılandırılmamış, lütfen model ayarlarında API anahtarı ekleyin", + "loadingProjectModel": "Proje modeli yükleniyor...", + "usingModel": "Kullanılan model", + "startGeneration": "Oluşturmayı Başlat", + "batchGenCompleted": "Toplu oluşturma tamamlandı! {{success}}/{{total}} dosya için başarıyla GA çiftleri oluşturuldu.", + "generationError": "Oluşturma sırasında hata oluştu: {{error}}", + "fetchProjectInfoFailed": "Proje bilgisi getirilemedi: {{status}}", + "fetchModelConfigFailed": "Model yapılandırması getirilemedi: {{status}}", + "fetchProjectModelError": "Proje model yapılandırması getirilirken hata", + "batchGenerationFailed": "Toplu GA çifti oluşturma başarısız", + "batchGenerationSuccess": "{{count}} dosya için başarıyla GA çiftleri oluşturuldu", + "selectAllFiles": "Tümünü Seç", + "deselectAllFiles": "Tümünü Kaldır" + }, + "batchEdit": { + "title": "Toplu Metin Bloğu Düzenleme", + "batchEdit": "Toplu Düzenle", + "batchEditTooltip": "Seçilen metin bloklarını toplu düzenle", + "position": "Ekleme Konumu", + "atBeginning": "Başa Ekle", + "atEnd": "Sona Ekle", + "contentToAdd": "Eklenecek İçerik", + "contentPlaceholder": "Metin bloklarına eklenecek içeriği girin...", + "contentRequired": "Lütfen eklenecek içeriği girin", + "contentHelp": "Bu içerik tüm seçilen metin bloklarına eklenecektir", + "preview": "Önizleme", + "allChunksSelected": "Tüm {{count}} metin bloğu seçildi", + "selectedChunks": "{{selected}} / {{total}} metin bloğu seçildi", + "processing": "İşleniyor...", + "applyToChunks": "{{count}} bloğa uygula", + "editSuccess": "{{count}} metin bloğu başarıyla düzenlendi", + "editFailed": "Toplu düzenleme başarısız", + "previewNote": "Yukarıdaki, seçilen ilk metin bloğunun önizlemesidir. Tüm seçilen metin blokları aynı değişikliği alacaktır" + }, + "errors": { + "projectIdRequired": "Proje ID'si boş olamaz", + "getDatasetsFailed": "Veri setleri alınamadı", + "getTagStatsFailed": "Etiket istatistikleri alınamadı", + "deleteFileFailed": "Dosya silinirken hata", + "recordNotFound": "Mevcut kayıt mevcut değil", + "mineruTokenNotFound": "Token yapılandırması bulunamadı, lütfen görev ayarlarında MinerU token yapılandırıldığını kontrol edin", + "mineruLocalUrlNotFound": "MinerU yerel URL yapılandırması bulunamadı, lütfen görev ayarlarında MinerU yerel URL yapılandırıldığını kontrol edin" + }, + "sampleData": { + "questionContent": "Soru içeriği", + "answerContent": "Cevap içeriği", + "cotContent": "Düşünce zinciri içeriği", + "domainLabel": "Alan etiketi", + "textChunk": "Metin bloğu" + }, + "exportDialog": { + "balancedExport": "Dengeli Dışa Aktarma", + "balancedExportTitle": "Dengeli Dışa Aktarma Ayarları", + "balancedExportDescription": "Dengeli veri seti dışa aktarımı elde etmek için alan etiketlerine göre her kategori için veri miktarını yapılandırın", + "quickSettings": "Hızlı Ayarlar", + "setAllTo50": "Tümünü 50'ye ayarla", + "setAllTo100": "Tümünü 100'e ayarla", + "setAllTo200": "Tümünü 200'e ayarla", + "customAmount": "Özel miktar", + "tagName": "Etiket Adı", + "availableCount": "Mevcut Sayı", + "exportCount": "Dışa Aktarma Sayısı", + "settings": "Ayarlar", + "totalExportCount": "Toplam dışa aktarma sayısı", + "tagCount": "Etiket sayısı", + "export": "Dışa Aktar" + }, + "imageDatasets": { + "title": "Görsel S&C Veri Seti", + "subtitle": "Görsel S&C veri setlerinizi yönetin ve optimize edin", + "description": "Görsel S&C veri setlerinizi yönetin ve optimize edin.", + "searchPlaceholder": "Soruları veya cevapları ara...", + "noAnswer": "Cevap yok", + "labels": "Etiketler", + "typeLabel": "Etiket", + "typeCustom": "Özel", + "typeText": "Metin", + "unscored": "Puanlanmamış", + "confirmed": "Onaylandı", + "unconfirmed": "Onaylanmadı", + "view": "Detayları Görüntüle", + "evaluate": "Kalite Değerlendirmesi", + "delete": "Sil", + "deleteConfirm": "Bu veri setini silmek istediğinizden emin misiniz?", + "imageName": "Görsel Adı", + "status": "Durum", + "scoreRange": "Puan Aralığı", + "noData": "Görsel veri seti yok", + "noDataTip": "Lütfen önce Görsel Yönetimi'nde S&C veri setleri oluşturun", + "fetchFailed": "Veri setleri getirilemedi", + "fetchDetailFailed": "Detaylar getirilemedi", + "deleteSuccess": "Başarıyla silindi", + "deleteFailed": "Silme başarısız", + "updateSuccess": "Başarıyla güncellendi", + "updateFailed": "Güncelleme başarısız", + "regenerateSuccess": "AI tanıma başarılı", + "regenerateFailed": "AI tanıma başarısız", + "notFound": "Veri seti bulunamadı", + "detail": "Detay", + "image": "Görsel", + "question": "Soru", + "answer": "Cevap", + "selectLabels": "Etiketleri Seç", + "noLabels": "Etiket seçilmedi", + "jsonPlaceholder": "JSON format verisi girin...", + "metadata": "Meta Veri", + "score": "Puan", + "tags": "Etiketler", + "addTag": "Etiket ekle...", + "note": "Not", + "notePlaceholder": "Not ekle...", + "modelInfo": "Model Bilgisi", + "createdAt": "Oluşturulma Tarihi", + "updatedAt": "Güncellenme Tarihi", + "exportTitle": "Görsel Veri Setini Dışa Aktar", + "exportFormat": "Dışa Aktarma Formatı", + "rawFormat": "Ham Format", + "customFormat": "Özel Format", + "exportImagesOption": "Görsel Dosyalarını Dışa Aktar", + "exportImagesDesc": "Tüm görselleri indirmek için ZIP dosyasına paketle", + "includeImagePath": "Veri Setine Görsel Yolunu Dahil Et", + "includeImagePathDesc": "Soru veya cevapta görsel yolu ekle (format: /images/görsel_adı)", + "systemPrompt": "Sistem İstemi (İsteğe Bağlı)", + "systemPromptPlaceholder": "Sistem istemi girin...", + "confirmedOnly": "Sadece Onaylananları Dışa Aktar", + "exportTip": "Etiket formatı cevapları otomatik olarak metne (virgülle ayrılmış) ayrıştırılacaktır", + "exportSuccess": "Veri seti başarıyla dışa aktarıldı", + "exportFailed": "Dışa aktarma başarısız", + "noDataToExport": "Dışa aktarılacak veri yok", + "exportImagesSuccess": "Görsel ZIP paketi başarıyla dışa aktarıldı", + "exportImagesFailed": "Görseller dışa aktarılamadı" + }, + "images": { + "resolution": "Çözünürlük", + "uploadTime": "Yükleme Zamanı", + "fileName": "Dosya Adı", + "title": "Görsel Yönetimi", + "importImages": "Görselleri İçe Aktar", + "searchPlaceholder": "Görsel adı ara...", + "hasQuestions": "Soru Durumu", + "hasDatasets": "Veri Seti Durumu", + "withQuestions": "Sorulu", + "withoutQuestions": "Sorusuz", + "withDatasets": "Veri Setli", + "withoutDatasets": "Veri Setsiz", + "noImages": "Görsel yok", + "questions": "Sorular", + "datasets": "Veri Setleri", + "generateQuestions": "Soru Oluştur", + "generateDataset": "Veri Seti Oluştur", + "deleteConfirm": "Bu görseli silmek istediğinizden emin misiniz?", + "deleteSuccess": "Başarıyla silindi", + "deleteFailed": "Silme başarısız", + "importTip": "Görsel içeren bir veya daha fazla dizin seçin. Tüm görseller projeye içe aktarılacaktır (yinelenen adlar üzerine yazılacaktır)", + "selectDirectory": "Dizin Seç", + "directoryPath": "Dizin Yolu", + "enterDirectoryPath": "örn., /Users/kullanıcıadı/Resimler", + "selectedDirectories": "Seçilen Dizinler", + "selectAtLeastOne": "Lütfen en az bir dizin seçin", + "importSuccess": "{{count}} görsel başarıyla içe aktarıldı", + "importFailed": "İçe aktarma başarısız", + "startImport": "İçe Aktarmayı Başlat", + "addDirectory": "Dizin Ekle", + "importFromDirectory": "Dizinden İçe Aktar", + "importFromPdf": "PDF'den İçe Aktar", + "pdfImportTip": "PDF dosyası seçin, sistem otomatik olarak görsellere dönüştürüp içe aktaracaktır", + "clickToSelectPdf": "PDF dosyası seçmek için tıklayın", + "supportedFormat": "Desteklenen format: PDF", + "fileSize": "Dosya boyutu", + "selectedFile": "Seçilen dosya", + "invalidPdfFile": "Lütfen geçerli bir PDF dosyası seçin", + "selectPdfFile": "Lütfen bir PDF dosyası seçin", + "pdfImportSuccess": "\"{{name}}\" PDF'sinden {{count}} görsel başarıyla içe aktarıldı", + "pdfImportFailed": "PDF içe aktarma başarısız", + "convertAndImport": "Dönüştür ve İçe Aktar", + "electronRequired": "Bu özellik masaüstü uygulamasını gerektirir", + "selectDirectoryFailed": "Dizin seçilemedi", + "imageName": "Görsel Adı", + "questionCount": "Soru Sayısı", + "questionCountHelp": "1-10 soru oluştur", + "currentModel": "Mevcut Model", + "selectModelFirst": "Lütfen önce bir model seçin", + "visionModelRequired": "Lütfen görsel yetenekli bir model seçin (örn., GPT-4 Vision, Claude, vb.)", + "countRange": "Soru sayısı 1-10 arasında olmalıdır", + "questionsGenerated": "{{count}} soru başarıyla oluşturuldu", + "generateFailed": "Oluşturma başarısız", + "question": "Soru", + "questionPlaceholder": "Sorunuzu girin...", + "questionRequired": "Lütfen bir soru girin", + "datasetGenerated": "Veri seti başarıyla oluşturuldu", + "autoGenerateQuestions": "Otomatik Soru Oluştur", + "autoGenerateConfirm": "Sistem, sorusu olmayan tüm görseller için otomatik olarak sorular oluşturacaktır. Bu işlem bir arka plan görevi oluşturacaktır, ilerlemeyi Görev Yönetimi'nde görüntüleyebilirsiniz.", + "taskCreated": "Görev başarıyla oluşturuldu, arka planda işleniyor", + "taskCreateFailed": "Görev oluşturulamadı", + "manualAnnotation": "Manuel Etiketleme", + "annotationTitle": "Görsel Etiketleme", + "imageInfo": "Görsel Bilgisi", + "annotatedCount": "Etiketlendi", + "selectQuestion": "Soru Seç veya Oluştur", + "selectQuestionPlaceholder": "Soru şablonu seçin...", + "universalQuestions": "Evrensel Sorular", + "independentQuestions": "Bağımsız Sorular", + "answerTypeText": "Metin", + "answerTypeLabel": "Etiket", + "answerTypeCustomFormat": "Özel Format", + "usedTimes": "{{count}} kez kullanıldı", + "answer": "Cevap", + "answerPlaceholder": "Cevap girin...", + "selectLabels": "Etiketleri Seç", + "availableLabels": "Mevcut Etiketler", + "noLabelsAvailable": "Mevcut etiket yok", + "addNewLabel": "Yeni etiket ekle...", + "selectedLabels": "Seçildi", + "customFormatAnswer": "Özel Format Cevabı", + "formatRequirement": "Format Gereksinimi", + "customFormatPlaceholder": "Gerekli formatta JSON girin...", + "note": "Not", + "notePlaceholder": "Not (isteğe bağlı)", + "saveAndContinue": "Kaydet ve Devam Et", + "noImageSelected": "Görsel seçilmedi", + "noTemplateSelected": "Lütfen bir soru seçin", + "answerRequired": "Lütfen bir cevap girin", + "invalidJsonFormat": "Geçersiz JSON formatı", + "annotationSuccess": "Etiketleme başarıyla kaydedildi", + "annotationFailed": "Etiketleme kaydedilemedi", + "allQuestionsAnnotated": "Bu görsel için tüm sorular etiketlendi", + "allImagesAnnotated": "Tüm görseller için tüm sorular etiketlendi", + "noQuestionsAssociated": "Bu görsel ile ilişkilendirilmiş soru yok", + "loadImageDetailFailed": "Görsel detayları yüklenemedi", + "answeredQuestions": "Etiketlenmiş Sorular", + "useTemplate": "Şablon Kullan", + "formatJson": "Formatla", + "jsonFormatHelp": "Lütfen geçerli JSON format verisi girin", + "imageLoadError": "Görsel yüklenemedi", + "annotateImage": "Görseli Etiketle", + "createQuestion": "Soru Oluştur", + "createTemplate": "Soru Şablonu Oluştur", + "aiGenerate": "AI Tanı", + "aiGenerateSuccess": "AI oluşturma başarılı", + "aiGenerateFailed": "AI oluşturma başarısız", + "missingParameters": "Gerekli parametreler eksik", + "selectNewQuestion": "Yeni Soru Seç", + "fetchTemplatesFailed": "Soru şablonları getirilemedi", + "createTemplateSuccess": "Soru şablonu başarıyla oluşturuldu", + "createTemplateFailed": "Soru şablonu oluşturulamadı", + "updateTemplateSuccess": "Soru şablonu başarıyla güncellendi", + "updateTemplateFailed": "Soru şablonu güncellenemedi", + "deleteTemplateSuccess": "Soru şablonu başarıyla silindi", + "deleteTemplateFailed": "Soru şablonu silinemedi", + "template": { + "management": "Soru Şablonlarını Yönet", + "create": "Şablon Oluştur", + "edit": "Şablonu Düzenle", + "question": "Soru İçeriği", + "description": "Açıklama", + "noTemplates": "Henüz soru şablonu yok, eklemek için oluştur butonuna tıklayın", + "deleteConfirm": "Bu soru şablonunu silmek istediğinizden emin misiniz?", + "used": "Kullanıldı", + "addLabel": "Etiket Ekle", + "customFormat": "Özel Format", + "customFormatHelp": "JSON format çıktı kısıtlaması girin", + "customFormatInfo": "Bu format, çıktı formatını kısıtlamak için LLM'e istem olarak sağlanacaktır", + "type": { + "label": "Soru Türü", + "universal": "Evrensel Soru", + "independent": "Bağımsız Soru" + }, + "answerType": { + "label": "Cevap Türü", + "text": "Metin", + "tags": "Etiketler", + "customFormat": "Özel Format" + }, + "errors": { + "questionRequired": "Lütfen soru içeriğini girin", + "labelsRequired": "Etiket türü sorular en az bir etiket gerektirir", + "customFormatRequired": "Lütfen özel format girin", + "invalidJson": "Geçersiz JSON formatı" + } + } + } +} diff --git a/easy-dataset-main/locales/zh-CN/translation.json b/easy-dataset-main/locales/zh-CN/translation.json new file mode 100644 index 0000000..74101f6 --- /dev/null +++ b/easy-dataset-main/locales/zh-CN/translation.json @@ -0,0 +1,1855 @@ +{ + "language": { + "switchToEnglish": "切换到英文", + "switchToChinese": "切换到中文", + "switcherTitle": "切换语言 / Change Language / Dil Değiştir", + "english": "English", + "chineseSimplified": "简体中文", + "turkish": "Türkçe", + "en": "EN", + "zh": "中" + }, + "theme": { + "switchToLight": "切换到亮色模式", + "switchToDark": "切换到暗色模式" + }, + "settings": { + "promptConfig": "提示词配置", + "promptsDescription": "配置项目中使用的各类自定义提示词,可用于人工干预数据集的生成效果。", + "globalPrompt": "全局提示词", + "questionPrompt": "生成问题提示词", + "answerPrompt": "生成答案提示词", + "labelPrompt": "问题打标提示词", + "domainTreePrompt": "构建领域树提示词", + "globalPromptPlaceholder": "请输入全局提示词(慎用,可能影响整体生成效果)", + "questionPromptPlaceholder": "请输入自定义生成问题的提示词", + "answerPromptPlaceholder": "请输入自定义生成答案的提示词", + "labelPromptPlaceholder": "请输入自定义问题打标的提示词(暂不支持配置)", + "domainTreePromptPlaceholder": "请输入自定义构建领域树的提示词", + "cleanPrompt": "数据清洗提示词", + "cleanPromptPlaceholder": "请输入自定义数据清洗的提示词", + "loadPromptsFailed": "加载提示词配置失败", + "savePromptsSuccess": "保存提示词配置成功", + "savePromptsFailed": "保存提示词配置失败", + "title": "项目设置", + "basicInfo": "基本信息", + "modelConfig": "模型配置", + "taskConfig": "任务配置", + "tabsAriaLabel": "设置选项卡", + "idNotEditable": "项目 ID 不可编辑", + "saveBasicInfo": "保存基本信息", + "saveSuccess": "保存成功", + "saveFailed": "保存失败", + "deleteSuccess": "删除成功", + "deleteFailed": "删除失败", + "fetchTasksFailed": "获取任务配置失败", + "saveTasksFailed": "保存任务配置失败", + "textSplitSettings": "文本分块设置", + "minLength": "最小长度", + "maxLength": "最大分割长度", + "textSplitDescription": "调整文本分割的长度范围,影响分割结果的粒度", + "splitType": "分块策略", + "splitTypeMarkdown": "文档结构分块(Markdown)", + "splitTypeMarkdownDesc": "根据文档中的标题自动分割文本,保持语义完整性,适合结构化清晰的 Markdown 文档", + "splitTypeRecursive": "文本结构分块(自定义分隔符)", + "splitTypeRecursiveDesc": "递归地尝试多级分隔符(可配置),先用优先级高的分隔符,再用次级分隔符,适合复杂文档", + "splitTypeText": "固定长度分块(字符)", + "splitTypeTextDesc": "按指定分隔符(可配置)切分文本,然后按指定长度组合,适合普通文本文件", + "splitTypeToken": "固定长度分块(Token)", + "splitTypeTokenDesc": "基于 Token 数量(而非字符数)分块", + "splitTypeCode": "程序代码智能分块", + "splitTypeCodeDesc": "根据不同编程语言的语法结构进行智能分块,避免在语法不完整处分割", + "splitTypeCustom": "自定义符号分块", + "splitTypeCustomDesc": "根据自定义符号进行文档分割,分隔符将被舍弃,分割的文本块不受块大小影响", + "codeLanguage": "代码语言", + "codeLanguageHelper": "选择要分块的代码语言,会根据语言特性进行智能分块", + "chunkSize": "块大小", + "chunkOverlap": "块重叠长度", + "separator": "分隔符", + "separatorHelper": "用于分割文本的分隔符,如 \n\n 表示空行", + "customSeparator": "自定义分隔符", + "customSeparatorHelper": "用于分割文本的自定义分符,如 --- 或 ===", + "separators": "分隔符列表", + "separatorsInput": "分隔符(逗号分隔)", + "separatorsHelper": "用逗号分隔的分隔符列表,按优先级排序", + "questionGenSettings": "问题生成设置", + "questionGenLength": "{{length}} 个字符生成一个问题", + "questionMaskRemovingProbability": "将 {{probability}}% 问题结尾的问号去除", + "questionGenDescription": "设置生成问题的最大长度", + "concurrencyLimit": "并发限制数量", + "concurrencyLimitHelper": "限制同时生成问题、生成数据集的任务数量", + "saveTaskConfig": "保存任务配置", + "pdfSettings": "PDF文件转换配置", + "minerUToken": "PDF 转换(MinerU API)Token 配置", + "minerUHelper": "MinerU Token 只有14天有效期,请及时更换Token", + "minerULocalUrl": "PDF 转换(MinerU Local)URL 配置", + "vision": "自定义视觉模型选择", + "visionConcurrencyLimit": "视觉模型并发限制", + "huggingfaceSettings": "Hugging Face 设置", + "huggingfaceToken": "Hugging Face Token", + "multiTurnSettings": "多轮对话数据集设置", + "multiTurnSystemPrompt": "系统提示词", + "multiTurnSystemPromptHelper": "设定AI助手的身份和行为规范", + "multiTurnScenario": "对话场景", + "multiTurnScenarioHelper": "描述对话的具体场景和目标", + "multiTurnRounds": "对话轮数:{{rounds}} 轮", + "multiTurnRoleA": "角色A设定(用户)", + "multiTurnRoleAHelper": "定义用户角色的身份和特征", + "multiTurnRoleB": "角色B设定(助手)", + "multiTurnRoleBHelper": "定义助手角色的身份和特征", + "multiTurnDescription": "多轮对话配置用于生成连贯的多轮对话数据集,支持自定义角色和场景", + "evalQuestionSettings": "测试集生成设置", + "evalQuestionSettingsDescription": "配置生成测试集时各题型的比例,比例为0表示不生成该类型题目", + "evalTrueFalseRatio": "判断题比例", + "evalSingleChoiceRatio": "单选题比例", + "evalMultipleChoiceRatio": "多选题比例", + "evalShortAnswerRatio": "固定短答案比例", + "evalOpenEndedRatio": "开放式回答比例", + "evalQuestionRatioHelper": "系统会根据设置的比例自动分配各题型的生成数量,所有比例之和不需要等于特定值", + "prompts": { + "selectPromptFirst": "请在左侧选择一个提示词", + "customized": "已自定义", + "editPrompt": "编辑提示词", + "restoreDefault": "恢复默认", + "promptType": "提示词类型", + "keyName": "键名", + "contentPlaceholder": "请输入自定义提示词内容...", + "restoreDefaultContent": "恢复默认内容", + "noPromptsAvailable": "没有可用的提示词", + "restoreSuccess": "已恢复为默认提示词", + "restoreFailed": "恢复默认提示词失败", + "deleteError": "删除提示词出错:", + "saveSuccess": "提示词保存成功", + "saveFailed": "提示词保存失败", + "saveError": "保存提示词出错:", + "createCustomPrompt": "创建自定义提示词", + "fetchContentError": "获取最新提示词内容失败:" + } + }, + "questions": { + "autoGenerateDataset": "自动生成数据集", + "autoGenerateDatasetTip": "创建后台批量处理任务:自动查询待生成答案的问题并生成数据集", + "generateSingleTurnDataset": "生成单轮对话数据集", + "generateSingleTurnDatasetDesc": "基于问题生成问答数据集", + "generateMultiTurnDataset": "生成多轮对话数据集", + "generateImageDataset": "生成图像问答数据集", + "generateMultiTurnDatasetDesc": "基于问题生成多轮对话数据集", + "multiTurnNotConfigured": "请先在项目设置中配置多轮对话相关参数", + "filterAll": "全部问题", + "filterAnswered": "已生成答案", + "filterUnanswered": "未生成答案", + "filterChunkNamePlaceholder": "按文本块名称筛选...", + "sourceTypeAll": "全部数据源", + "sourceTypeText": "文本数据源", + "sourceTypeImage": "图片数据源", + "title": "问题", + "confirmDeleteTitle": "确认删除问题", + "confirmDeleteContent": "您确定要删除问题\"{{question}}\"吗?此操作不可恢复。", + "deleting": "正在删除问题...", + "batchDeleteTitle": "确认批量删除问题", + "batchDeleting": "正在删除 {{count}} 个问题...", + "deleteSuccess": "问题删除成功", + "deleteFailed": "删除问题失败", + "batchDeleteSuccess": "成功删除 {{count}} 个问题", + "batchDeletePartial": "删除完成,成功: {{success}}, 失败: {{failed}}", + "batchDeleteFailed": "批量删除问题失败", + "noQuestionsSelected": "请先选择问题", + "batchGenerateStart": "开始生成 {{count}} 个问题的数据集", + "invalidQuestionKey": "无效的问题键", + "listView": "问题列表", + "treeView": "领域树视图", + "selectAll": "全选", + "selectedCount": "已选择 {{count}} 个问题", + "totalCount": "共 {{count}} 个问题", + "searchPlaceholder": "搜索问题或标签...", + "searchMatch": "匹配", + "searchNotMatch": "不匹配", + "deleteSelected": "删除所选", + "batchGenerate": "批量构造数据集", + "generating": "正在生成数据集", + "generatingProgress": "已完成: {{completed}}/{{total}}", + "generatedCount": "已生成 {{count}} 个数据集", + "pleaseWait": "请稍候...", + "selectAllLimitReached": "已选中 {{count}} 条问题(已达最大限制)", + "selectAllFailed": "全选操作失败,请稍后重试", + "createSuccess": "问题创建成功", + "updateSuccess": "问题更新成功", + "operationSuccess": "操作成功", + "operationFailed": "操作失败", + "editQuestion": "编辑问题", + "questionContent": "问题内容", + "sourceType": "数据源类型", + "sourceType.text": "文本", + "sourceType.image": "图片", + "selectChunk": "选择文本块", + "searchChunk": "搜索文本块...", + "selectImage": "选择图片", + "searchImage": "搜索图片...", + "selectTag": "选择标签", + "searchTag": "搜索标签...", + "createQuestion": "创建问题", + "createNormalQuestion": "创建普通问题", + "createQuestionTemplate": "创建问题模板", + "questionPlaceholder": "请输入问题内容", + "noChunkSelected": "请先选择文本块", + "fetchTemplatesFailed": "获取问题模板失败", + "createTemplateSuccess": "问题模板创建成功", + "createTemplateFailed": "创建问题模板失败", + "updateTemplateSuccess": "问题模板更新成功", + "updateTemplateFailed": "更新问题模板失败", + "deleteTemplateSuccess": "问题模板删除成功", + "deleteTemplateFailed": "删除问题模板失败", + "exportQuestions": "导出问题集", + "exportScope": "导出范围", + "exportAll": "导出全部({{count}} 个问题)", + "exportSelected": "导出已选({{count}} 个问题)", + "exportFormat": "导出格式", + "txtFormat": "纯文本(仅问题内容)", + "exportSuccess": "问题集导出成功", + "exportFailed": "问题集导出失败", + "template": { + "management": "问题模板", + "create": "创建问题模板", + "edit": "编辑问题模板", + "question": "问题内容", + "description": "提示词", + "descriptionHelp": "用于在后续 AI 生成这个问题模版相关的答案时,加入到整体提示词中,以用于干预最终答案生成的结果", + "noTemplates": "暂无问题模板,点击创建按钮添加", + "deleteConfirm": "确定要删除这个问题模板吗?", + "used": "已使用", + "addLabel": "添加标签", + "customFormat": "自定义格式", + "customFormatHelp": "输入 JSON 格式的输出约束", + "customFormatInfo": "此格式将作为提示词提供给大模型,用于约束输出格式", + "sourceTypeInfo": "数据源类型", + "sourceType": { + "label": "数据源类型", + "image": "图像(用于对图像生成QA)", + "text": "文本(用于对文本块生成QA)" + }, + "answerType": { + "label": "答案输出格式", + "text": "普通文本", + "tags": "标签数组", + "customFormat": "自定义格式" + }, + "errors": { + "questionRequired": "请输入问题内容", + "labelsRequired": "标签类型问题至少需要一个标签", + "customFormatRequired": "请输入自定义格式", + "invalidJson": "JSON 格式不正确" + }, + "autoGenerate": "创建模板后自动生成问题", + "autoGenerateHelpText": "将为项目中的所有文本块自动创建基于此模板的问题", + "autoGenerateHelpImage": "将为项目中的所有图片自动创建基于此模板的问题", + "confirmAutoGenerate": "确认自动生成问题", + "confirmAutoGenerateTextMessage": "您选择了自动生成问题。系统将为项目中的所有文本块创建基于此模板的问题。", + "confirmAutoGenerateImageMessage": "您选择了自动生成问题。系统将为项目中的所有图片创建基于此模板的问题。", + "autoGenerateWarning": "此操作可能会创建大量问题,请确认后继续。", + "autoGenerateSuccess": "成功为 {{count}} 个数据源创建了问题", + "autoGeneratePartialFail": "成功创建 {{success}} 个问题,{{fail}} 个失败", + "autoGenerateFailed": "自动生成问题失败" + }, + "noTagSelected": "请选择标签", + "deleteConfirm": "确认要删除这个问题吗?", + "generateMultiTurn": "生成多轮对话", + "multiTurnGenerated": "多轮对话数据集生成成功!" + }, + "common": { + "dataSource": "数据源", + "menu": "菜单", + "openMenu": "打开导航菜单", + "all": "全部", + "jumpTo": "跳转至", + "unknownError": "未知错误", + "create": "创建", + "confirm": "确认", + "edit": "编辑", + "delete": "删除", + "save": "保存", + "cancel": "取消", + "complete": "完成", + "close": "关闭", + "add": "添加", + "remove": "删除", + "loading": "加载中...", + "yes": "是", + "no": "否", + "confirmDelete": "确认删除吗?此操作不可撤销!", + "saving": "保存中...", + "deleting": "删除中...", + "actions": "操作", + "confirmDeleteDataSet": "确认删除数据集吗?操作不可撤销!", + "noData": "无", + "failed": "失败", + "success": "成功", + "backToList": "返回列表", + "label": "标签", + "confirmDeleteDescription": "确认删除吗?此操作不可恢复。", + "more": "更多", + "import": "导入", + "export": "导出", + "fetchError": "获取数据出错", + "confirmDeleteQuestion": "确认删除此问题吗?此操作不可恢复。", + "deleteSuccess": "删除成功", + "visitGitHub": "访问GitHub仓库", + "syncOldData": "同步文件数据", + "copy": "复制", + "copied": "已复制", + "enabled": "已启用", + "disabled": "已禁用", + "generating": "正在生成...", + "processing": "处理中...", + "items": "项", + "detailInfo": "详细信息", + "reset": "重置", + "apply": "应用", + "mainNavigation": "主导航", + "goHome": "回到首页", + "goToHomePage": "回到首页", + "mobileNavigation": "移动端导航菜单", + "navigation": "导航", + "closeMenu": "关闭菜单", + "documentation": "文档", + "viewOnGitHub": "在 GitHub 上查看", + "back": "返回", + "refresh": "刷新", + "expand": "展开全部", + "collapse": "收起内容" + }, + "home": { + "title": "Easy Dataset", + "subtitle": "一个强大的大型语言模型微调数据集创建工具", + "createProject": "创建项目", + "searchDataset": "搜索公开数据集" + }, + "projects": { + "reuseConfig": "复用模型配置", + "noReuse": "不复用配置", + "selectProject": "选择项目", + "fetchFailed": "获取项目列表失败", + "fetchError": "获取项目列表出错", + "loading": "正在加载您的项目...", + "createFailed": "创建项目失败", + "createError": "创建项目出错", + "createNew": "创建新项目", + "saveFailed": "保存项目失败", + "id": "项目ID", + "name": "项目名称", + "description": "项目描述", + "questions": "问题", + "datasets": "数据集", + "evalDatasets": "评估集", + "tokens": "Tokens", + "lastUpdated": "最后更新", + "viewDetails": "查看详情", + "createFirst": "创建第一个项目", + "noProjects": "暂无项目", + "notExist": "项目不存在", + "createProject": "创建项目", + "deleteConfirm": "确认删除项目吗?此操作不可恢复。", + "deleteSuccess": "项目删除成功", + "deleteFailed": "删除项目失败", + "backToHome": "返回首页", + "deleteConfirmTitle": "确认删除项目", + "title": "项目管理", + "openDirectory": "打开项目目录" + }, + "textSplit": { + "dragToUpload": "拖拽文件到此处上传", + "fileList": "文件列表", + "autoGenerateQuestions": "自动提取问题", + "autoGenerateQuestionsTip": "创建后台批量处理任务:自动查询待生成问题的文本块并提取问题", + "exportChunks": "导出文本块", + "allChunks": "全部文本块", + "generatedQuestions2": "已生成问题", + "ungeneratedQuestions": "未生成问题", + "contentKeyword": "文本块内容", + "contentKeywordPlaceholder": "输入关键词搜索文本块内容", + "characterRange": "字数范围", + "questionStatus": "问题状态", + "noFilesUploaded": "暂未上传文件", + "unknownFile": "未知文件", + "fetchFilesFailed": "获取文件列表出错", + "editTag": "编辑标签", + "deleteTag": "删除标签", + "addTag": "添加标签", + "selectedCount": "已选择 {{count}} 个文本块", + "totalCount": "共 {{count}} 个文本块", + "batchGenerateQuestions": "批量生成问题", + "batchDeleteChunks": "批量删除", + "batchDeleteChunksConfirmTitle": "确认批量删除", + "batchDeleteChunksConfirmMessage": "您确定要删除选中的 {{count}} 个文本块吗?此操作不可恢复。", + "uploadedDocuments": "已上传 {{count}}个文档", + "title": "文件处理", + "uploadNewDocument": "上传新文件", + "selectFile": "选择文件(支持多个)", + "markdownOnly": "目前仅支持上传 Markdown (.md) 格式文件(建议上传同一领域的文件)", + "supportedFormats": "支持的格式: .pdf .md, .txt, .docx(建议上传同一领域的文件)", + "uploadAndProcess": "上传并处理文件", + "selectedFiles": "已选择文件({{count}})", + "oneFileMessage": "一个项目限制处理一个文件,如需上传新文件请先删除现有文件", + "mutilFileMessage": "上传新文件后会重新构建领域树", + "noChunks": "暂无文本块,请先上传并处理文件", + "chunkDetails": "文本块详情: {{chunkId}}", + "fetchChunksFailed": "获取文本块失败", + "fetchChunksError": "获取文本块出错", + "fileResultReceived": "获取到文件结果", + "fileUploadSuccess": "文件上传成功", + "splitTextFailed": "文本分割失败", + "splitTextError": "文本分割出错", + "deleteChunkFailed": "删除文本块失败", + "deleteChunkError": "删除文本块出错", + "selectModelFirst": "请先选择一个模型,可以在顶部导航栏选择", + "modelNotAvailable": "选择的模型不可用,请重新选择", + "generateQuestionsFailed": "为文本块 {{chunkId}} 生成问题失败", + "questionsGenerated": "已生成 {{total}} 个问题", + "customSplitMode": "自定义分块模式", + "customSplitInstructions": "选择文本内容以添加分块点。系统会在选中位置添加分割标记。", + "splitPointsList": "已添加的分块点", + "saveSplitPoints": "保存分块点", + "confirmCustomSplitTitle": "确认替换原有分块", + "confirmCustomSplitMessage": "注意:自定义分块将替换该文件之前自动分块的结果。确定要继续保存吗?", + "customSplitSuccess": "自定义分块保存成功", + "customSplitFailed": "自定义分块保存失败", + "missingRequiredData": "缺少必要的数据", + "chunksPreview": "分块字数预览", + "chunk": "文本块", + "characters": "字", + "questionsGeneratedSuccess": "成功为文本块生成了 {{total}} 个问题", + "generateQuestionsForChunkFailed": "为文本块 {{chunkId}} 生成问题失败", + "generateQuestionsForChunkError": "为文本块 {{chunkId}} 生成问题出错", + "generateQuestionsError": "生成问题出错", + "partialSuccess": "部分文本块生成问题成功 ({{successCount}}/{{total}}),{{errorCount}} 个文本块失败", + "allSuccess": "成功为 {{successCount}} 个文本块生成了 {{totalQuestions}} 个问题", + "fileDeleted": "文件 {{fileName}} 已删除,刷新文本块列表", + "tabs": { + "smartSplit": "智能分割", + "domainAnalysis": "领域分析" + }, + "loading": "加载中...", + "fetchingDocuments": "正在获取文件数据", + "processing": "处理中...", + "progressStatus": "已选择 {{total}} 个文本块,已处理完成 {{completed}} 个", + "processingPleaseWait": "正在努力处理中,请稍候!", + "oneFileLimit": "已有上传文件,不允许选择新文件", + "unsupportedFormat": "不支持的文件格式: {{files}}", + "modelInfoParseError": "解析模型信息失败", + "uploadFailed": "上传失败,请刷新页面后重试!", + "uploadSuccess": "成功上传 {{count}} 个文件", + "deleteFailed": "删除文件失败", + "deleteSuccess": "文件 {{fileName}} 已成功删除", + "generatedQuestions": "已生成 {{count}} 个问题", + "generatedEvalQuestions": "已生成 {{count}} 道测试题", + "generateQuestions": "生成问题", + "generateEvalQuestions": "生成测试集", + "evalQuestionsGeneratedSuccess": "成功生成 {{total}} 道测评题目", + "generateEvalQuestionsFailed": "生成测评题目失败", + "dataCleaning": "数据清洗", + "batchDataCleaning": "批量数据清洗", + "autoDataCleaning": "自动数据清洗", + "autoDataCleaningTip": "创建后台批量处理任务:自动对所有文本块进行数据清洗", + "autoEvalGeneration": "自动生成评估集", + "autoEvalGenerationTip": "创建后台批量处理任务:自动为所有未生成评估题目的文本块生成评估数据集", + "autoTasks": "自动任务", + "dataCleaningSuccess": "数据清洗完成,原长度: {{originalLength}},清洗后长度: {{cleanedLength}}", + "dataCleaningFailed": "为文本块 {{chunkId}} 数据清洗失败", + "dataCleaningForChunkSuccess": "文本块 {{chunkId}} 数据清洗完成", + "dataCleaningForChunkFailed": "为文本块 {{chunkId}} 数据清洗失败", + "dataCleaningForChunkError": "为文本块 {{chunkId}} 数据清洗出错", + "dataCleaningPartialSuccess": "部分文本块数据清洗成功 ({{successCount}}/{{total}}),{{errorCount}} 个文本块失败", + "dataCleaningAllSuccess": "成功为 {{successCount}} 个文本块完成数据清洗", + "charsCount": "字符", + "pdfProcess": "检测到PDF文件,请选择 PDF 文件处理方式!", + "pdfProcessStatus": "共 {{total}} 个文件,{{completed}} 个已处理完成", + "pdfPageProcessStatus": "正在处理 {{fileName}} 共{{total}}页,{{completed}}页已完成转换", + "pdfProcessing": "正在转换文件...", + "pdfProcessingFailed": "文件处理失败!", + "selectPdfProcessingStrategy": "请选择PDF文件处理方式:", + "pdfProcessingStrategyDefault": "默认", + "pdfProcessingStrategyDefaultHelper": "使用内置PDF解析策略", + "pdfProcessingStrategyMinerUHelper": "使用MinerU API解析,请先配置MinerU API Token", + "pdfProcessingStrategyVision": "自定义视觉模型", + "pdfProcessingStrategyVisionHelper": "使用自定义视觉模型解析", + "pdfProcessingToast": "上传文件成功,系统将创建后台任务解析文件!", + "pdfProcessingLoading": "正在执行文件处理任务,请等待任务完成后再上传新文件...", + "pdfProcessingWaring": "正在执行文件处理任务,建议当任务完成后再进行其他操作,否则可能会影响数据生成质量!", + "basicPdfParsing": "基础 PDF 解析", + "basicPdfParsingDesc": "可识别简单的 PDF 文件,包括关键目录结构,速度更快", + "mineruApiDesc": "可识别复杂 PDF 文件,包括公式、图表(需要配置 MinerU API Key)", + "mineruLocalDesc": "可识别复杂 PDF 文件,包括公式、图表(需要配置 MinerU Local URL)", + "mineruApiDescDisabled": "请先到【项目配置 - 任务配置】设置 MinerU Token", + "mineruLocalDisabled": "请先到【项目配置 - 任务配置】设置 MinerU Local URL", + "mineruWebPlatform": "MinerU 在线平台解析", + "mineruWebPlatformDesc": "可识别复杂 PDF 文件,包括公式、图表(需跳转到其他网站)", + "mineruSelected": "已选择使用 MinerU 解析PDF", + "mineruLocalSelected": "已选择使用 MinerU Local 解析PDF", + "customVisionModel": "自定义视觉模型解析", + "customVisionModelDesc": "可识别复杂 PDF 文件,包括公式、图表(需在模型配置增加视觉模型配置)", + "customVisionModelSelected": "已选择使用视觉大模型 {{name}}({{provider}}) 解析PDF)", + "defaultSelected": "已选择使用默认内置策略解析PDF", + "download": "下载文件", + "deleteFile": "删除文件", + "batchDelete": "批量删除 ({{count}})", + "batchDeleteTitle": "批量删除文件", + "batchDeleteConfirm": "确定要删除选中的 {{count}} 个文件吗?此操作不可恢复。", + "batchDeleteSuccess": "成功删除 {{count}} 个文件", + "batchDeleteFailed": "批量删除失败", + "searchFiles": "搜索文件名...", + "searchResults": "找到 {{count}} 个文件(共 {{total}} 个)", + "noSearchResults": "未找到包含 \"{{searchTerm}}\" 的文件", + "noResultsOnCurrentPage": "当前页面没有搜索结果,请返回第一页查看", + "noDataOnCurrentPage": "当前页面没有数据", + "viewChunk": "查看文本块", + "editChunk": "编辑文本块 {{chunkId}}", + "editChunkSuccess": "文本块编辑成功", + "editChunkFailed": "文本块编辑失败", + "editChunkError": "编辑文本块时出错", + "deleteFileWarning": "警告:删除文件将同时删除以下相关内容", + "deleteFileWarningChunks": "所有关联的文本块", + "deleteFileWarningQuestions": "所有文本块生成的问题", + "deleteFileWarningDatasets": "所有问题生成的数据集", + "domainTree": { + "firstUploadTitle": "领域树生成", + "uploadTitle": "文件上传 - 领域树处理", + "deleteTitle": "文件删除 - 领域树处理", + "reviseOption": "修订领域树", + "reviseDesc": "针对新增或删除的文件信息,对当前的领域树进行修正,只影响变更的部分", + "rebuildOption": "重建领域树", + "rebuildDesc": "基于所有文件的目录信息重新生成完整的领域树", + "keepOption": "保持不变", + "keepDesc": "保持当前领域树结构不变,不做任何修改" + } + }, + "domain": { + "title": "领域知识树", + "addRootTag": "添加一级标签", + "addFirstTag": "添加第一个标签", + "noTags": "暂无领域标签树数据", + "docStructure": "文档目录结构", + "noToc": "暂无目录结构,请先上传并处理文件", + "editTag": "编辑标签", + "deleteTag": "删除标签", + "addChildTag": "添加子标签", + "deleteTagConfirmTitle": "删除标签", + "deleteTagConfirmMessage": "您确定要删除标签 \"{{tag}}\" 吗?", + "deleteWarning": "此操作将删除该标签及其所有子标签、问题和数据集,且无法恢复!", + "dialog": { + "addTitle": "添加标签", + "editTitle": "编辑标签", + "addChildTitle": "添加子标签", + "inputRoot": "请输入新的一级标签名称", + "inputEdit": "请编辑标签名称", + "inputChild": "请为\"{label}\"添加子标签", + "labelName": "标签名称", + "saving": "保存中...", + "save": "保存", + "deleteConfirm": "确定要删除标签\"{label}\"吗?", + "deleteWarning": "此操作将同时删除所有子标签,且无法恢复。", + "emptyLabel": "标签名称不能为空" + }, + "tabs": { + "tree": "领域树", + "structure": "目录结构" + }, + "errors": { + "saveFailed": "保存标签失败" + }, + "messages": { + "updateSuccess": "标签更新成功" + } + }, + "export": { + "alpacaSettings": "Alpaca 格式设置", + "questionFieldType": "问题字段类型", + "useInstruction": "使用 instruction 字段", + "useInput": "使用 input 字段", + "customInstruction": "自定义 instruction 字段内容", + "instructionPlaceholder": "请输入固定的指令内容", + "instructionHelperText": "当使用 input 字段时,可以在这里指定固定的 instruction 内容", + "title": "导出", + "format": "数据集风格", + "fileFormat": "文件格式", + "systemPrompt": "系统提示词", + "systemPromptPlaceholder": "请输入系统提示词...", + "ReasoninglanguagePlaceholder": "请输入Reasoning language : English or Chinese or others", + "Reasoninglanguage": "推理语言", + "onlyConfirmed": "仅导出已确认数据", + "example": "格式示例", + "confirmExport": "确认导出", + "includeCOT": "包含思维链", + "cotDescription": "包含最终答案前的推理过程", + "customFormat": "自定义格式", + "customFormatSettings": "自定义格式设置", + "questionFieldName": "问题字段名", + "answerFieldName": "答案字段名", + "cotFieldName": "思维链字段名", + "includeLabels": "包含标签", + "includeChunk": "包含文本块", + "questionOnly": "仅导出问题", + "localTab": "导出到本地", + "llamaFactoryTab": "在 LLaMA Factory 中使用", + "huggingFaceTab": "上传至 Hugging Face", + "configExists": "已存在配置文件", + "configPath": "配置文件路径", + "updateConfig": "更新 LLaMA Factory 配置", + "noConfig": "暂无配置文件,点击下方按钮生成", + "generateConfig": "生成 LLaMA Factory 配置", + "huggingFaceComingSoon": "HuggingFace 导出功能即将推出", + "uploadToHuggingFace": "上传至 HuggingFace", + "datasetName": "数据集名称", + "datasetNameHelp": "格式:用户名/数据集名称", + "privateDataset": "私有数据集", + "datasetSettings": "数据集设置", + "exportOptions": "导出选项", + "uploadSuccess": "数据集已成功上传至 HuggingFace", + "viewOnHuggingFace": "在 HuggingFace查看", + "noTokenWarning": "未找到 HuggingFace 令牌。请在项目设置中配置令牌。", + "goToSettings": "前往设置", + "tokenHelp": "您可以从HuggingFace设置页面获取令牌", + "multilingualThinkingFormat": "Multilingual‑Thinking", + "sampleInstruction": "人类指令(必填)", + "sampleOutput": "模型回答(必填)", + "sampleSystem": "系统提示词(选填)", + "sampleInstruction2": "第二个指令", + "sampleOutput2": "第二个回答", + "sampleSystemShort": "系统提示词", + "fixedInstruction": "固定的指令内容", + "sampleInput": "人类问题(必填)", + "sampleInput2": "第二个问题", + "sampleInputOptional": "人类输入(选填)", + "sampleUserMessage": "人类指令", + "sampleAssistantMessage": "模型回答", + "sampleAnalysis": "模型的思维链内容", + "sampleFinal": "模型回答", + "sampleThinking": "模型的思维链内容", + "fetchLabelStatsError": "获取标签统计失败:" + }, + "datasets": { + "loadingDataset": "正在加载数据集详情...", + "datasetNotFound": "未找到数据集", + "optimizeTitle": "AI 优化", + "optimizeAdvice": "优化建议", + "optimizePlaceholder": "请输入您对答案的改进建议,AI将根据您的建议优化答案和思维链", + "generatingDataset": "正在生成数据集", + "aiOptimizeAdvicePlaceholder": "请输入您对答案的改进建议,AI将根据您的建议优化答案和思维链", + "aiOptimizeAdvice": "请输入您对答案的改进建议,AI将根据您的建议优化答案和思维链", + "aiOptimize": "AI 智能优化", + "generating": "正在生成数据集", + "partialSuccess": "部分问题生成数据集成功 ({{successCount}}/{{total}}),{{failCount}} 个问题失败", + "generateError": "生成数据集失败", + "management": "数据集", + "question": "问题", + "filterAll": "全部", + "filterConfirmed": "已确认", + "filterUnconfirmed": "未确认", + "createdAt": "创建时间", + "model": "使用模型", + "domainTag": "领域标签", + "cot": "思维链", + "answer": "回答", + "chunkId": "文本块", + "confirmed": "已确认", + "noTag": "无标签", + "noData": "暂无数据", + "rowsPerPage": "每页行数", + "pagination": "{{from}}-{{to}} 共 {{count}}", + "confirmDeleteMessage": "确定要删除这个数据集吗?这个操作不可撤销。", + "questionLabel": "问题", + "fetchFailed": "获取数据集失败", + "deleteFailed": "删除数据集失败", + "deleteSuccess": "删除成功", + "exportSuccess": "数据集导出成功", + "exportFailed": "导出失败", + "exportProgress": "导出进度", + "exportingData": "正在导出数据集", + "processedCount": "已处理 {{processed}} / {{total}} 条", + "exportInProgress": "正在获取数据,请稍候...", + "exportFinalizing": "正在生成文件,即将完成...", + "loading": "加载数据集...", + "stats": "共 {{total}} 个数据集,已确认 {{confirmed}} 个({{percentage}}%)", + "selected": "共选中{{ count }}个数据集", + "batchconfirmDeleteMessage": "您确定要删除选中的 {{count}} 个数据集吗?此操作不可恢复。", + "batchDelete": "批量删除", + "batchDeleteProgress": "已完成: {{completed}}/{{total}}", + "batchDeleteCount": "已生成: {{count}}", + "evaluation": "数据集标注", + "rating": "评分", + "ratingExcellent": "优秀", + "ratingGood": "良好", + "ratingAverage": "一般", + "ratingPoor": "较差", + "ratingVeryPoor": "很差", + "ratingUnrated": "未评分", + "customTags": "自定义标签", + "addCustomTag": "添加自定义标签...", + "note": "备注", + "addNote": "添加备注...", + "noNote": "暂无备注", + "clickToAddNote": "点击添加备注...", + "enterNote": "请输入备注...", + "noteShortcuts": "Ctrl+Enter 保存,Esc 取消", + "aiEvaluation": "AI 质量评估", + "addToEval": "添加到评估数据集", + "addToEvalSuccess": "成功添加到评估数据集", + "addToEvalFailed": "添加失败", + "generateEvalVariant": "生成评估集变体", + "generateVariantFailed": "生成变体失败", + "saveVariantSuccess": "已保存到评估数据集", + "saveVariantFailed": "保存失败", + "evalVariantTitle": "生成评估集变体", + "evalVariantPreviewTitle": "确认生成的题目", + "saveToEval": "保存到评估集", + "evalVariantConfigHint": "请选择生成的题目类型和数量,AI 将基于当前问答对进行改写。", + "questionType": "题目类型", + "typeOpenEnded": "开放式问答", + "typeSingleChoice": "单选题", + "typeMultipleChoice": "多选题", + "typeTrueFalse": "判断题", + "typeShortAnswer": "简答题", + "generateCount": "生成数量", + "evalVariantPreviewHint": "您可以编辑生成的题目,确认无误后保存到评估集。", + "questionIndex": "题目 {{index}}", + "options": "选项 (JSON数组)", + "optionsHint": "例如: [\"选项A\", \"选项B\"]", + "answerArrayHint": "多选题答案请输入数组,如 [\"A\", \"C\"]", + "answerBoolHint": "判断题答案请输入 ✅ 或 ❌", + "generate": "生成", + "updateSuccess": "更新成功", + "updateFailed": "更新失败", + "searchPlaceholder": "搜索数据集...", + "fieldQuestion": "问题", + "fieldAnswer": "回答", + "fieldCOT": "思维链", + "fieldLabel": "领域标签", + "moreFilters": "更多", + "filtersTitle": "筛选条件", + "filterConfirmationStatus": "确认状态", + "filterCotStatus": "思维链状态", + "filterHasCot": "包含思维链", + "filterNoCot": "不包含思维链", + "filterScoreRange": "评分范围", + "filterNoteKeyword": "备注关键字", + "filterNoteKeywordPlaceholder": "请输入备注关键字...", + "filterChunkName": "文本块名称", + "filterChunkNamePlaceholder": "请输入文本块名称...", + "filterCustomTag": "自定义标签", + "resetFilters": "重置", + "applyFilters": "应用筛选", + "viewDetails": "查看详情", + "datasetDetail": "数据集详情", + "metadata": "元数据", + "confirmSave": "确认保留", + "unconfirm": "取消确认", + "unconfirming": "取消确认中...", + "uncategorized": "未分类", + "questionCount": "{{count}} 个问题", + "source": "来源", + "generateDataset": "生成数据集", + "generateNotImplemented": "生成数据集功能未实现", + "generateSuccess": "成功生成数据集:{{question}}", + "generateFailed": "生成数据集失败:{{error}}", + "noTagsAndQuestions": "暂无标签和问题", + "answerCount": "{{count}} 个答案", + "answered": "已生成答案", + "enableShortcuts": "翻页快捷键", + "shortcutsHelp": "按 ← 向前,按 → 向后,按 y 确认,按 d 删除", + "filterDistill": "是否蒸馏数据集", + "filterDistillYes": "蒸馏数据集", + "filterDistillNo": "非蒸馏数据集", + "evaluate": "质量评估", + "evaluating": "评估中...", + "batchEvaluate": "自动质量评估", + "selectModelFirst": "请先选择模型", + "evaluateSuccess": "评估完成!评分:{{score}}/5", + "evaluateFailed": "评估失败", + "evaluateError": "评估失败: {{error}}", + "batchEvaluateStarted": "批量评估任务已启动,将在后台进行处理", + "batchEvaluateStartFailed": "启动批量评估失败", + "batchEvaluateFailed": "批量评估失败: {{error}}", + "scoreRange": "{{min}} - {{max}} 分", + "singleTurn": "单轮问答数据集", + "multiTurn": "多轮对话数据集", + "imageQA": "图片问答数据集", + "conversationDetail": "多轮对话详情", + "conversationContent": "对话内容", + "basicInfo": "基本信息", + "firstQuestion": "首轮问题", + "conversationScenario": "对话场景", + "conversationRounds": "对话轮数", + "modelUsed": "使用模型", + "qualityScore": "质量评分", + "notes": "备注", + "createTime": "创建时间", + "notSet": "未设置", + "noTags": "无标签", + "noNotes": "无备注", + "notEvaluated": "暂未评估", + "round": "第 {{round}} 轮", + "system": "系统", + "user": "用户", + "assistant": "助手", + "confirmDelete": "确认删除", + "confirmDeleteConversation": "确定要删除这个多轮对话数据集吗?此操作不可恢复。", + "conversationNotFound": "对话数据集不存在", + "fetchDataFailed": "获取数据失败", + "saveFailed": "保存失败", + "saveSuccess": "保存成功", + "saving": "保存中...", + "inputTagsPlaceholder": "输入标签,用空格分隔", + "addNotesPlaceholder": "添加备注信息", + "noConversations": "暂无多轮对话数据集", + "notRated": "未评分", + "minScore": "最低评分", + "maxScore": "最高评分", + "unconfirmed": "未确认" + }, + "rating": { + "veryPoor": "很差", + "poor": "差", + "belowAverage": "偏差", + "fair": "一般", + "average": "中等", + "good": "良好", + "veryGood": "很好", + "excellent": "优秀", + "outstanding": "杰出", + "perfect": "完美", + "unrated": "未评分" + }, + "tags": { + "noTags": "暂无标签", + "addTag": "添加标签...", + "addCustomTag": "添加自定义标签", + "maxTagsReached": "最多可添加 {{maxTags}} 个标签", + "availableTagsHint": "可从已有标签中选择,或输入新标签" + }, + "import": { + "title": "导入", + "fileUpload": "文件上传", + "fileUploadDescription": "上传本地文件导入数据集", + "uploadFile": "上传文件", + "supportedFormats": "支持 JSON、JSONL、CSV 格式文件", + "dragDropFile": "拖拽文件到此处或点击选择文件", + "dropFileHere": "松开以上传文件", + "maxFileSize": "最大文件大小: 50MB", + "processingFile": "正在处理文件...", + "uploadedFiles": "已上传文件", + "uploadError": "文件上传失败,请检查文件格式是否正确", + "mapFields": "字段映射", + "importing": "导入中", + "fieldMapping": "字段映射", + "mappingDescription": "请将源数据的字段映射到目标字段。系统已自动识别可能的映射关系,您可以根据需要调整。", + "selectMapping": "选择字段映射", + "questionField": "问题字段", + "answerField": "答案字段", + "cotField": "思维链字段", + "tagsField": "标签字段", + "selectField": "选择字段", + "questionDesc": "用户的问题或输入内容(必选)", + "answerDesc": "AI的回答或输出内容(必选)", + "cotDesc": "思维链或推理过程(可选)", + "tagsDesc": "标签数组,多个标签用逗号分隔(可选)", + "dataPreview": "数据预览", + "previewNote": "显示前3条记录,每个字段值最多显示100个字符", + "confirmMapping": "确认映射", + "requiredFields": "请至少选择问题和答案字段的映射", + "mappingRequired": "问题和答案字段为必选项", + "duplicateMapping": "不能将多个目标字段映射到同一个源字段", + "noPreviewData": "没有可预览的数据", + "preparingData": "准备数据...", + "uploadingData": "上传数据...", + "processing": "处理中... {{processed}}/{{total}}", + "completed": "导入完成", + "importStats": "导入统计", + "total": "总计: {{count}}", + "success": "成功: {{count}}", + "failed": "失败: {{count}}", + "source": "数据源", + "description": "描述", + "errors": "错误信息", + "moreErrors": "还有 {{count}} 个错误未显示...", + "importSuccess": "数据集导入完成!", + "enterDatasetName": "请输入数据集名称", + "noDatasetFound": "未找到匹配的数据集", + "complete": "完成", + "addToEval": "添加到评估数据集", + "addToEvalSuccess": "成功添加到评估数据集", + "addToEvalFailed": "添加失败", + "generateEvalVariant": "生成评估集变体", + "selectModelFirst": "请先选择模型", + "generateVariantFailed": "生成变体失败", + "saveVariantSuccess": "已保存到评估数据集", + "saveVariantFailed": "保存失败", + "evalVariantTitle": "生成评估集变体", + "evalVariantHint": "AI 已根据原问答对生成了新的测试变体,您可以手动编辑后保存。", + "saveToEval": "保存到评估集", + "evalVariantConfigHint": "请选择生成的题目类型和数量,AI 将基于当前问答对进行改写。", + "questionType": "题目类型", + "typeOpenEnded": "开放式问答", + "typeSingleChoice": "单选题", + "typeMultipleChoice": "多选题", + "typeTrueFalse": "判断题", + "typeShortAnswer": "简答题", + "generateCount": "生成数量", + "evalVariantPreviewHint": "您可以编辑生成的题目,确认无误后保存到评估集。", + "questionIndex": "题目 {{index}}", + "options": "选项 (JSON数组)", + "optionsHint": "例如: [\"选项A\", \"选项B\"]", + "answerArrayHint": "多选题答案请输入数组,如 [\"A\", \"C\"]", + "answerBoolHint": "判断题答案请输入 ✅ 或 ❌", + "evalVariantPreviewTitle": "确认生成的题目", + "generate": "生成" + }, + "update": { + "newVersion": "新版本", + "newVersionAvailable": "发现新版本", + "currentVersion": "当前版本", + "latestVersion": "最新版本", + "downloadNow": "立即下载", + "downloading": "正在下载:", + "installNow": "立即安装", + "updating": "更新中...", + "updateNow": "立即更新", + "viewRelease": "下载最新版本", + "checking": "正在检查更新...", + "noUpdates": "已是最新版本", + "updateError": "更新出错", + "updateSuccess": "更新成功", + "restartRequired": "需要重启应用", + "restartNow": "立即重启", + "restartLater": "稍后重启" + }, + "datasetSquare": { + "title": "数据集广场", + "subtitle": "发现和探索各种公开数据集资源,助力您的模型训练和研究", + "searchPlaceholder": "搜索数据集关键词...", + "searchVia": "通过", + "categoryTitle": "数据集分类", + "categories": { + "all": "全部", + "popular": "热门推荐", + "chinese": "中文资源", + "english": "英文资源", + "research": "研究数据", + "multimodal": "多模态" + }, + "foundResources": "找到 {{count}} 个数据集资源", + "currentFilter": "当前筛选: {{category}}", + "noDatasets": "没有找到符合条件的数据集", + "tryOtherCategories": "请尝试其他分类或返回全部数据集查看", + "dataset": "数据集", + "viewDataset": "查看数据集" + }, + "playground": { + "title": "模型测试", + "selectModelFirst": "请选择至少一个模型", + "sendFirstMessage": "发送第一条消息开始测试", + "inputMessage": "输入消息...", + "send": "发送", + "outputMode": "输出方式", + "normalOutput": "普通输出", + "streamingOutput": "流式输出", + "clearConversation": "清空对话", + "selectModelMax3": "选择模型(最多3个)", + "reasoningProcess": "推理过程" + }, + "chunks": { + "title": "文本块", + "defaultTitle": "默认标题" + }, + "documentation": "文档", + "models": { + "configNotFound": "未找到模型配置", + "parseError": "解析模型配置失败", + "fetchFailed": "获取模型失败", + "saveFailed": "保存模型配置失败", + "pleaseSelectModel": "请至少选择一个模型", + "title": "模型管理", + "add": "添加模型", + "unselectedModel": "未选择模型", + "unconfiguredAPIKey": "未配置 API Key", + "saveAllModels": "保存所有模型配置", + "edit": "编辑", + "delete": "删除", + "modelId": "模型 ID", + "modelName": "模型名称", + "modelNamePlaceholder": "请输入模型名称(可选,默认为模型 ID)", + "modelIdPlaceholder": "模型名称(可自定义输入)", + "endpoint": "接口地址", + "apiKey": "API密钥", + "provider": "提供商(可自定义输入)", + "localModel": "本地模型", + "apiKeyConfigured": "API Key 已经配置", + "apiKeyNotConfigured": "API Key 未配置", + "temperature": "模型温度", + "maxTokens": "最大生成 Token 数", + "maxTokensInputTip": "滑块范围:1-{{max}}。你也可以输入任意正整数。", + "topP": "Top P", + "type": "模型标签", + "text": "语言模型", + "vision": "视觉模型", + "typeTips": "如果希望使用自定义视觉模型解析PDF请务必配置至少一个视觉大模型", + "refresh": "刷新模型列表", + "configuredModels": "已配置模型", + "unconfiguredModels": "未配置模型", + "noConfiguredModels": "暂无已配置模型", + "noUnconfiguredModels": "暂无未配置模型", + "checkEndpointHealth": "检查端点健康度", + "checkAllEndpointHealth": "一键检查全部端点", + "endpointHealthy": "端点健康", + "endpointCheckFailed": "端点检查失败", + "endpointMissing": "端点为空", + "endpointReachableModelMissing": "端点可访问,但当前模型不在返回列表中", + "healthCheckSummary": "健康检查完成:正常 {{okCount}} 个,失败 {{failCount}} 个", + "checking": "检查中...", + "healthy": "健康", + "reachable": "可达", + "unhealthy": "异常", + "notChecked": "未检查" + }, + "stats": { + "ongoingProjects": "正在运行的项目", + "questionCount": "问题数量", + "generatedDatasets": "已生成的数据集", + "supportedModels": "已支持的模型" + }, + "migration": { + "title": "【重要】历史数据迁移", + "description": "为了提升大量数据集的检索性能,自 1.3.1 版本起,Easy Dataset 将文件存储方式变更为本地数据库存储,检测到您有历史项目尚未进行迁移,在完成迁移前,您将无法访问这些项目,请尽快完成迁移!", + "projectsList": "未迁移的项目", + "migrate": "开始迁移", + "migrating": "迁移中...", + "success": "成功迁移 {{count}} 个项目", + "failed": "迁移失败", + "checkFailed": "检查未迁移项目失败", + "checkError": "检查未迁移项目出错", + "starting": "正在启动迁移任务...", + "processing": "正在处理迁移任务...", + "completed": "迁移已完成", + "startFailed": "启动迁移任务失败", + "statusFailed": "获取迁移状态失败", + "taskNotFound": "迁移任务不存在", + "progressStatus": "已迁移 {{completed}}/{{total}} 个项目", + "openDirectory": "打开项目目录", + "deleteDirectory": "删除项目目录", + "confirmDelete": "确定要删除此项目目录吗?此操作不可恢复。", + "openDirectoryFailed": "打开项目目录失败", + "deleteDirectoryFailed": "删除项目目录失败" + }, + "distill": { + "title": "数据蒸馏", + "generateRootTags": "生成顶级标签", + "generateSubTags": "生成子标签", + "generateQuestions": "生成问题", + "generateRootTagsTitle": "生成顶级领域标签", + "generateSubTagsTitle": "为 {{parentTag}} 生成子标签", + "generateQuestionsTitle": "为 {{tag}} 生成问题", + "parentTag": "父标签", + "parentTagPlaceholder": "请输入父标签名称(如:体育、科技等)", + "parentTagHelp": "输入一个领域主题,系统将基于此生成相关标签", + "tagCount": "标签数量", + "tagCountHelp": "输入要生成的标签数量,最大为100个", + "questionCount": "问题数量", + "questionCountHelp": "输入要生成的问题数量,最大为100个", + "generatedTags": "已生成的标签", + "generatedQuestions": "已生成的问题", + "generateTags": "生成标签", + "tagPath": "标签路径", + "noTags": "暂无标签", + "noQuestions": "暂无问题", + "clickGenerateButton": "点击上方的生成按钮开始创建标签", + "selectModelFirst": "请先选择一个模型", + "selectModel": "选择模型", + "generateTagsError": "生成标签失败", + "generateQuestionsError": "生成问题失败", + "deleteTagConfirmTitle": "确认要删除标签吗?将关联删除所有该标签下的子标签、问题、数据集", + "editTagTitle": "编辑标签", + "tagName": "标签名称", + "labelRequired": "标签名称不能为空", + "tagUpdateSuccess": "标签更新成功", + "tagUpdateFailed": "标签更新失败", + "unknownTag": "未知标签", + "autoDistillButton": "全自动蒸馏数据集", + "autoDistillTitle": "全自动蒸馏数据集配置", + "distillTopic": "蒸馏主题", + "tagLevels": "标签层级", + "tagLevelsHelper": "设置层级数量,最大为{{max}}级", + "tagsPerLevel": "每层标签数量", + "tagsPerLevelHelper": "每个父标签下生成的子标签数量,最大为{{max}}个", + "questionsPerTag": "每个标签问题数量", + "questionsPerTagHelper": "每个叶子标签生成的问题数量,最大为{{max}}个", + "estimationInfo": "任务预估信息", + "estimatedTags": "预计生成标签数量", + "estimatedQuestions": "预计生成问题数量", + "currentTags": "当前标签数量", + "currentQuestions": "当前问题数量", + "newTags": "预计新增标签数量", + "newQuestions": "预计新增问题数量", + "startAutoDistill": "开始自动蒸馏", + "autoDistillProgress": "自动蒸馏进度", + "overallProgress": "整体进度", + "tagsProgress": "标签构建进度", + "questionsProgress": "问题生成进度", + "currentStage": "当前阶段", + "realTimeLogs": "实时日志", + "waitingForLogs": "等待日志输出...", + "autoDistillStarted": "{{time}} 自动蒸馏任务开始", + "autoDistillInsufficientError": "当前配置不会产生新的标签或问题,请调整参数", + "stageInitializing": "初始化中...", + "stageBuildingLevel1": "正在构建第一层标签", + "stageBuildingLevel2": "正在构建第二层标签", + "stageBuildingLevel3": "正在构建第三层标签", + "stageBuildingLevel4": "正在构建第四层标签", + "stageBuildingLevel5": "正在构建第五层标签", + "stageBuildingQuestions": "正在生成问题", + "stageBuildingDatasets": "正在生成数据集", + "stageCompleted": "任务已完成", + "datasetsProgress": "数据集生成进度", + "rootTopicHelperText": "默认以项目名称作为顶级蒸馏主题,如需更改,请到项目设置中更改项目名称。", + "addChildTag": "生成子标签", + "datasetType": "数据集类型", + "singleTurnDataset": "单轮对话数据集", + "multiTurnDataset": "多轮对话数据集", + "bothDatasetTypes": "两种数据集都生成", + "autoDistillTaskDetail": "自动蒸馏任务: {{topic}}", + "backgroundTaskCreated": "后台蒸馏任务已创建,可在任务管理中查看进度", + "backgroundTaskFailed": "创建后台任务失败", + "taskExecutionError": "任务执行出错: {{error}}" + }, + "tasks": { + "pending": "有 {{count}} 个任务处理中", + "completed": "全部任务已完成", + "title": "任务管理中心", + "loading": "加载任务列表...", + "empty": "暂无任务记录", + "confirmDelete": "确认删除该任务?", + "confirmAbort": "确认中断该任务?任务将停止执行。", + "deleteSuccess": "任务已删除", + "deleteFailed": "删除任务失败", + "abortSuccess": "任务已中断", + "abortFailed": "中断任务失败", + "status": { + "processing": "处理中", + "completed": "已完成", + "failed": "失败", + "aborted": "已中断", + "unknown": "未知" + }, + "types": { + "text-processing": "文本处理", + "file-processing": "文件处理", + "data-cleaning": "数据清洗", + "question-generation": "问题生成", + "answer-generation": "答案生成", + "multi-turn-generation": "多轮对话生成", + "eval-generation": "评估集生成", + "image-question-generation": "图片问题生成", + "data-distillation": "数据蒸馈", + "pdf-processing": "PDF解析" + }, + "filters": { + "status": "任务状态", + "type": "任务类型" + }, + "actions": { + "refresh": "刷新任务列表", + "delete": "删除任务", + "abort": "中断任务" + }, + "table": { + "type": "任务类型", + "status": "状态", + "progress": "进度", + "note": "备注", + "createTime": "创建时间", + "endTime": "完成时间", + "duration": "运行时间", + "model": "使用模型", + "detail": "任务详情", + "actions": "操作" + }, + "duration": { + "seconds": "{{seconds}}秒", + "minutes": "{{minutes}}分 {{seconds}}秒", + "hours": "{{hours}}小时 {{minutes}}分" + }, + "fetchFailed": "获取任务列表失败", + "createSuccess": "任务创建成功", + "createFailed": "任务创建失败", + "multiTurnCreateSuccess": "多轮对话数据集任务创建成功", + "notes": { + "selectedChunks": "已选择 {{count}} 个文本块", + "fileBatch": "文件处理参数:{{count}} 个文件(策略:{{strategy}})", + "jsonParams": "任务参数已配置", + "noChunksQuestion": "没有需要生成问题的文本块", + "noChunksCleaning": "没有需要清洗的文本块", + "processingFailed": "任务失败:{{error}}", + "questionSummary": "已处理 {{processed}}/{{total}},成功 {{succeeded}},失败 {{failed}},生成问题 {{generated}}", + "datasetSummary": "已处理 {{processed}}/{{total}},成功 {{succeeded}},失败 {{failed}},生成数据集 {{generated}}", + "cleaningSummary": "已处理 {{processed}}/{{total}},成功 {{succeeded}},失败 {{failed}},原文长度 {{original}},清洗后 {{cleaned}}", + "genericSummary": "已处理 {{processed}}/{{total}},成功 {{succeeded}},失败 {{failed}}" + } + }, + "gaPairs": { + "title": "文体-受众对管理", + "loading": "正在加载文体-受众对...", + "addPair": "添加文体-受众对", + "saveChanges": "保存更改", + "saving": "保存中...", + "restoreBackup": "恢复备份", + "noGaPairsTitle": "未找到文体-受众对", + "noGaPairsDescription": "为此文件生成AI驱动的文体-受众对", + "generateGaPairs": "生成文体-受众对", + "generating": "生成中...", + "generateMore": "生成更多文体-受众对", + "activePairs": "活跃的文体-受众对 ({{active}}/{{total}})", + "pairNumber": "文体-受众对 #{{number}}", + "active": "活跃", + "deleteTooltip": "删除文体-受众对", + "genre": "文体", + "genreDescription": "文体描述", + "audience": "受众", + "audienceDescription": "受众描述", + "addDialogTitle": "添加新的文体-受众对", + "genreTitle": "文体标题", + "audienceTitle": "受众标题", + "genreTitlePlaceholder": "请输入文体标题...", + "genreDescPlaceholder": "详细描述该文体...", + "audienceTitlePlaceholder": "请输入受众标题...", + "audienceDescPlaceholder": "详细描述目标受众...", + "cancel": "取消", + "addPairButton": "添加文体-受众对", + "requiredFields": "文体标题和受众标题为必填项", + "restoredFromBackup": "已从备份恢复", + "allPairsDeleted": "已成功删除所有文体-受众对", + "pairsSaved": "已成功保存 {{count}} 个文体-受众对", + "additionalPairsGenerated": "成功生成了 {{count}} 个额外的文体-受众对。总计:{{total}}", + "validationError": "文体-受众对 {{number}}:文体和受众标题为必填项", + "loadError": "无法加载文体-受众对:{{error}}", + "generateError": "生成文体-受众对失败", + "saveError": "保存文体-受众对失败", + "noActiveModel": "请在设置中配置AI模型后再生成文体-受众对。", + "contentTooShort": "文件内容过短或不适合生成文体-受众对。", + "configError": "AI模型配置错误。可能缺少必要的依赖项。", + "serverError": "服务器错误 ({{status}})。请稍后重试。", + "emptyResponse": "生成服务返回空响应", + "generationFailed": "生成失败", + "saveOperationFailed": "保存操作失败", + "serviceNotAvailable": "文体-受众对生成服务不可用。请检查您的API配置。", + "requestFailed": "请求失败 ({{status}})。请重试。", + "internalServerError": "发生内部服务器错误。", + "batchGenerate": "批量生成文体-受众对", + "batchGenerateDescription": "将为选中的 {{count}} 个文件批量生成文体-受众对,该操作可能需要一些时间。", + "appendMode": "追加模式", + "appendModeDescription": "为已有文体-受众对的文件生成更多文体-受众对,而不是覆盖", + "selectAtLeastOneFile": "请先选择至少一个文件", + "noDefaultModel": "未设置默认模型,请先在项目设置中配置模型", + "incompleteModelConfig": "模型配置不完整,请检查模型设置", + "missingApiKey": "模型未配置API密钥,请在模型设置中添加API密钥", + "loadingProjectModel": "加载项目模型中...", + "usingModel": "使用模型", + "startGeneration": "开始生成", + "batchGenCompleted": "批量生成完成!成功为 {{success}}/{{total}} 个文件生成了文体-受众对。", + "generationError": "生成过程发生错误: {{error}}", + "fetchProjectInfoFailed": "获取项目信息失败: {{status}}", + "fetchModelConfigFailed": "获取模型配置失败: {{status}}", + "fetchProjectModelError": "获取项目模型配置时出错", + "batchGenerationFailed": "批量生成文体-受众对失败", + "batchGenerationSuccess": "成功为 {{count}} 个文件生成文体-受众对", + "selectAllFiles": "全选", + "deselectAllFiles": "取消全选", + "batchGenerateTitle": "批量生成文体-受众对", + "generationMode": "生成方式", + "aiGenerateMode": "AI 生成", + "manualAddMode": "手动添加", + "genreDesc": "文体描述", + "audienceDesc": "受众描述", + "manualGaPairRequired": "请填写文体标题和受众标题", + "batchAddManual": "批量添加" + }, + "batchEdit": { + "title": "批量编辑文本块", + "batchEdit": "批量编辑", + "batchEditTooltip": "批量编辑选中的文本块", + "position": "添加位置", + "atBeginning": "在开头添加", + "atEnd": "在结尾添加", + "contentToAdd": "要添加的内容", + "contentPlaceholder": "请输入要添加到文本块的内容...", + "contentRequired": "请输入要添加的内容", + "contentHelp": "此内容将被添加到所有选中的文本块中", + "preview": "预览效果", + "allChunksSelected": "已选择全部 {{count}} 个文本块", + "selectedChunks": "已选择 {{selected}} / {{total}} 个文本块", + "processing": "处理中...", + "applyToChunks": "应用到 {{count}} 个文本块", + "editSuccess": "成功编辑了 {{count}} 个文本块", + "editFailed": "批量编辑失败", + "previewNote": "以上是第一个选中文本块的预览效果,所有选中的文本块都将进行相同的修改" + }, + "errors": { + "projectIdRequired": "项目ID不能为空", + "getDatasetsFailed": "获取数据集失败", + "getTagStatsFailed": "获取标签统计失败", + "deleteFileFailed": "删除文件出错", + "recordNotFound": "当前记录不存在", + "mineruTokenNotFound": "未找到token配置,请检查任务设置中是否配置了MinerU token", + "mineruLocalUrlNotFound": "未找到MinerU本地URL配置,请检查任务设置中是否配置了MinerU本地URL" + }, + "sampleData": { + "questionContent": "问题内容", + "answerContent": "答案内容", + "cotContent": "思维链过程内容", + "domainLabel": "领域标签", + "textChunk": "文本块" + }, + "exportDialog": { + "balancedExport": "平衡导出", + "balancedExportTitle": "平衡导出设置", + "balancedExportDescription": "根据领域标签配置每个类别的数据量,实现数据集的平衡导出", + "quickSettings": "快速设置", + "setAllTo50": "全部设为50", + "setAllTo100": "全部设为100", + "setAllTo200": "全部设为200", + "customAmount": "自定义数量", + "tagName": "标签名称", + "availableCount": "可用数量", + "exportCount": "导出数量", + "settings": "设置", + "totalExportCount": "总导出数量", + "tagCount": "标签数量", + "export": "导出" + }, + "imageDatasets": { + "title": "图片问答数据集", + "subtitle": "管理和优化您的图片问答数据集", + "description": "管理和优化您的图片问答数据集。", + "searchPlaceholder": "搜索问题或答案...", + "noAnswer": "暂无答案", + "labels": "标签", + "typeLabel": "标签", + "typeCustom": "自定义JSON", + "typeText": "普通文本", + "unscored": "未评分", + "confirmed": "已确认", + "unconfirmed": "未确认", + "view": "查看详情", + "evaluate": "质量评估", + "delete": "删除", + "deleteConfirm": "确定要删除这个数据集吗?", + "imageName": "图片名称", + "status": "状态", + "scoreRange": "评分范围", + "noData": "暂无图片数据集", + "noDataTip": "请先在图片管理中生成问答数据集", + "fetchFailed": "获取数据集失败", + "fetchDetailFailed": "获取详情失败", + "deleteSuccess": "删除成功", + "deleteFailed": "删除失败", + "updateSuccess": "更新成功", + "updateFailed": "更新失败", + "regenerateSuccess": "AI 识别成功", + "regenerateFailed": "AI 识别失败", + "notFound": "数据集不存在", + "detail": "详情", + "image": "图片", + "question": "问题", + "answer": "答案", + "selectLabels": "选择标签", + "noLabels": "未选择标签", + "jsonPlaceholder": "输入 JSON 格式数据...", + "metadata": "元数据", + "score": "评分", + "tags": "标签", + "addTag": "添加标签...", + "note": "备注", + "notePlaceholder": "添加备注信息...", + "modelInfo": "模型信息", + "createdAt": "创建时间", + "updatedAt": "更新时间", + "exportTitle": "导出图片数据集", + "exportFormat": "导出格式", + "rawFormat": "原始格式", + "customFormat": "自定义格式", + "exportImagesOption": "导出图片文件", + "exportImagesDesc": "将所有图片打包成 ZIP 压缩包一起下载,下载完成后可手动解压到和数据集文件同一目录下的 Images 文件夹中", + "includeImagePath": "在数据集中包含图片路径", + "includeImagePathDesc": "在问题或答案中添加图片路径(格式:/images/图片名称)", + "systemPrompt": "系统提示词(可选)", + "systemPromptPlaceholder": "输入系统提示词...", + "confirmedOnly": "仅导出已确认的数据集", + "exportTip": "标签格式的答案将自动解析为文本(逗号分隔)", + "exportSuccess": "数据集导出成功", + "exportFailed": "导出失败", + "noDataToExport": "没有可导出的数据", + "exportImagesSuccess": "图片压缩包导出成功", + "exportImagesFailed": "图片导出失败" + }, + "images": { + "resolution": "分辨率", + "uploadTime": "上传时间", + "fileName": "文件名", + "title": "图片管理", + "importImages": "导入图片", + "searchPlaceholder": "搜索图片名称...", + "hasQuestions": "问题状态", + "hasDatasets": "数据集状态", + "withQuestions": "已生成问题", + "withoutQuestions": "未生成问题", + "withDatasets": "已生成数据集", + "withoutDatasets": "未生成数据集", + "noImages": "暂无图片", + "noImagesDescription": "开始导入图片,创建您的第一个图片数据集", + "preview": "预览", + "questions": "问题", + "datasets": "数据集", + "datasetCount": "数据集数", + "generateQuestions": "生成问题", + "generateDataset": "生成数据集", + "deleteConfirm": "确定要删除这张图片吗?", + "deleteSuccess": "删除成功", + "deleteFailed": "删除失败", + "batchDelete": "批量删除", + "selectImagesToDelete": "请选择要删除的图片", + "batchDeleteConfirm": "确定要删除选中的 {{count}} 张图片吗?", + "batchDeleteSuccess": "成功删除 {{count}} 张图片", + "batchDeletePartialSuccess": "成功删除 {{success}} 张,失败 {{fail}} 张", + "batchDeleteFailed": "批量删除失败", + "importTip": "选择一个或多个包含图片的目录,所有图片将被导入到项目中(同名图片将被覆盖)", + "selectDirectory": "选择目录", + "directoryPath": "目录路径", + "enterDirectoryPath": "例如:/Users/username/Pictures", + "selectedDirectories": "已选择的目录", + "selectAtLeastOne": "请至少选择一个目录", + "importSuccess": "成功导入 {{count}} 张图片", + "importFailed": "导入失败", + "startImport": "开始导入", + "addDirectory": "添加目录", + "importFromDirectory": "从目录导入", + "importFromPdf": "从 PDF 导入", + "importFromZip": "从压缩包导入", + "pdfImportTip": "选择 PDF 文件,系统会自动将其转换为图片并导入", + "zipImportTip": "选择 ZIP 压缩包文件,系统会自动解压并导入其中的图片", + "clickToSelectPdf": "点击选择 PDF 文件", + "clickToSelectZip": "点击选择 ZIP 文件", + "supportedFormat": "支持格式:PDF", + "supportedZipFormat": "支持格式:ZIP", + "fileSize": "文件大小", + "selectedFile": "已选择文件", + "invalidPdfFile": "请选择有效的 PDF 文件", + "invalidZipFile": "请选择有效的 ZIP 文件", + "selectPdfFile": "请选择 PDF 文件", + "selectZipFile": "请选择 ZIP 文件", + "pdfImportSuccess": "成功从 PDF \"{{name}}\" 导入 {{count}} 张图片", + "pdfImportFailed": "PDF 导入失败", + "zipImportSuccess": "成功从压缩包 \"{{name}}\" 导入 {{count}} 张图片", + "zipImportFailed": "压缩包导入失败", + "convertAndImport": "转换并导入", + "extractAndImport": "解压并导入", + "electronRequired": "此功能需要在桌面应用中使用", + "selectDirectoryFailed": "选择目录失败", + "imageName": "图片名称", + "questionCount": "问题数量", + "questionCountHelp": "生成1-10个问题", + "size": "大小", + "dimensions": "尺寸", + "currentModel": "当前模型", + "selectModelFirst": "请先选择一个模型", + "visionModelRequired": "请选择支持视觉的模型(如 GPT-4 Vision、Claude 等)", + "countRange": "问题数量应在1-10之间", + "questionsGenerated": "成功生成 {{count}} 个问题", + "generateFailed": "生成失败", + "question": "问题", + "questionPlaceholder": "请输入您想问的问题...", + "questionRequired": "请输入问题", + "datasetGenerated": "数据集生成成功", + "autoGenerateQuestions": "自动提取问题", + "autoGenerateConfirm": "系统将为所有未生成问题的图片自动生成问题。此操作将创建一个后台任务,您可以在任务管理中查看进度。", + "taskCreated": "任务创建成功,正在后台处理", + "taskCreateFailed": "任务创建失败", + "manualAnnotation": "手动标注", + "annotationTitle": "图片标注", + "imageInfo": "图片信息", + "annotatedCount": "已标注", + "selectQuestion": "选择或创建问题", + "selectQuestionPlaceholder": "请选择问题模板...", + "universalQuestions": "通用问题", + "independentQuestions": "独立问题", + "answerTypeText": "文字", + "answerTypeLabel": "标签", + "answerTypeCustomFormat": "自定义格式", + "usedTimes": "使用 {{count}} 次", + "answer": "答案", + "answerPlaceholder": "请输入答案...", + "selectLabels": "选择标签", + "availableLabels": "可选标签", + "noLabelsAvailable": "暂无可选标签", + "addNewLabel": "添加新标签...", + "selectedLabels": "已选择", + "customFormatAnswer": "自定义格式答案", + "formatRequirement": "格式要求", + "customFormatPlaceholder": "请输入符合格式的 JSON...", + "note": "备注", + "notePlaceholder": "备注信息(可选)", + "saveAndContinue": "保存并继续", + "noImageSelected": "未选择图片", + "noTemplateSelected": "请选择问题", + "answerRequired": "请输入答案", + "invalidJsonFormat": "JSON 格式不正确", + "annotationSuccess": "标注保存成功", + "annotationFailed": "保存标注失败", + "allQuestionsAnnotated": "当前图片所有问题已标注完成", + "allImagesAnnotated": "所有图片的问题都已标注完成", + "noQuestionsAssociated": "当前图片未关联任何问题", + "loadImageDetailFailed": "加载图片详情失败", + "answeredQuestions": "已标注问题", + "useTemplate": "使用模板", + "formatJson": "格式化", + "jsonFormatHelp": "请输入有效的JSON格式数据", + "imageLoadError": "图片加载失败", + "annotate": "标注", + "annotateImage": "标注图片", + "createQuestion": "创建问题", + "createTemplate": "创建问题模板", + "aiGenerate": "AI 识别", + "aiGenerateSuccess": "AI 生成成功", + "aiGenerateFailed": "AI 生成失败", + "missingParameters": "缺少必要参数", + "selectNewQuestion": "选择新问题", + "fetchTemplatesFailed": "获取问题模板失败", + "createTemplateSuccess": "问题模板创建成功", + "createTemplateFailed": "创建问题模板失败", + "updateTemplateSuccess": "问题模板更新成功", + "updateTemplateFailed": "更新问题模板失败", + "deleteTemplateSuccess": "问题模板删除成功", + "deleteTemplateFailed": "删除问题模板失败", + "template": { + "management": "管理问题模板", + "create": "创建模板", + "edit": "编辑模板", + "question": "问题内容", + "description": "描述", + "noTemplates": "暂无问题模板,点击创建按钮添加", + "deleteConfirm": "确定要删除这个问题模板吗?", + "used": "已使用", + "addLabel": "添加标签", + "customFormat": "自定义格式", + "customFormatHelp": "输入 JSON 格式的输出约束", + "customFormatInfo": "此格式将作为提示词提供给大模型,用于约束输出格式", + "type": { + "label": "问题类型", + "universal": "通用问题", + "independent": "独立问题" + }, + "answerType": { + "label": "答案类型", + "text": "文字", + "tags": "标签", + "customFormat": "自定义格式" + }, + "errors": { + "questionRequired": "请输入问题内容", + "labelsRequired": "标签类型问题至少需要一个标签", + "customFormatRequired": "请输入自定义格式", + "invalidJson": "JSON 格式不正确" + } + } + }, + "monitoring": { + "title": "资源监控看板", + "timeRange": { + "24h": "24小时", + "7d": "近7天", + "30d": "近30天" + }, + "filters": { + "allProjects": "所有项目", + "allProviders": "所有提供商", + "allStatus": "全部状态" + }, + "status": { + "success": "成功", + "failed": "失败" + }, + "actions": { + "export": "导出报表" + }, + "stats": { + "totalTokens": "总 Token 消耗", + "avgTokensPerCall": "平均 Token 消耗/次", + "totalCalls": "总调用次数", + "avgLatency": "平均响应耗时", + "inputOutput": "输入: {{input}} · 输出: {{output}}", + "successCalls": "{{count}} 成功", + "failedCalls": "{{count}} 失败", + "failureRate": "{{rate}}% 失败率", + "basedOnSuccessCalls": "基于 {{count}} 次成功请求", + "noSuccessCalls": "暂无成功请求" + }, + "charts": { + "tokenTrend": "Token 消耗趋势", + "inputLegend": "输入", + "outputLegend": "输出", + "distributionTitle": "Token 消耗分布 (按模型)", + "distributionSubtitle": "不同模型的资源消耗占比", + "tokensTooltip": "{{value}}K Tokens" + }, + "table": { + "title": "详细使用明细", + "searchPlaceholder": "搜索项目、模型或失败原因...", + "empty": "暂无数据", + "rowsPerPage": "每页行数:", + "columns": { + "projectName": "项目名称", + "provider": "模型提供商", + "model": "模型名称", + "status": "状态", + "failureReason": "失败原因", + "inputTokens": "输入 TOKEN", + "outputTokens": "输出 TOKEN", + "totalTokens": "TOTAL", + "calls": "调用次数", + "avgLatency": "平均耗时" + } + }, + "errors": { + "fetchSummaryFailed": "获取监控汇总数据失败", + "fetchLogsFailed": "获取监控日志失败" + } + }, + "eval": { + "title": "评估", + "datasets": "评估数据集", + "tasks": "自动评估任务", + "datasetsTitle": "评估数据集", + "datasetsDescription": "管理和查看所有生成的评估测试题目", + "tasksTitle": "评估任务", + "tasksComingSoon": "功能开发中", + "tasksComingSoonHint": "评估任务功能即将上线,敬请期待", + "totalQuestions": "题目总数", + "questionType": "题型", + "question": "问题", + "answer": "答案", + "options": "选项", + "correct": "正确", + "wrong": "错误", + "sourceChunk": "来源文本块", + "tags": "标签", + "tagsPlaceholder": "输入标签,多个标签用逗号分隔", + "note": "备注", + "detail": "详情", + "notFound": "未找到该题目", + "noData": "暂无评估数据", + "noDataHint": "请先在文本分割页面生成评估测试集", + "searchPlaceholder": "搜索题目内容...", + "cardView": "卡片视图", + "listView": "列表视图", + "deleteSelected": "删除选中 ({{count}})", + "deleteConfirmTitle": "确认删除", + "deleteConfirmMessage": "确定要删除 {{count}} 个题目吗?此操作不可撤销。", + "questionTypes": { + "true_false": "判断题", + "single_choice": "单选题", + "multiple_choice": "多选题", + "short_answer": "简答题", + "open_ended": "开放题" + } + }, + "evalDatasets": { + "import": { + "title": "导入评估数据集", + "questionType": "题型", + "selectTypeFirst": "请先选择题型", + "selectFile": "请选择要导入的文件", + "invalidFileType": "不支持的文件格式,请上传 json、xls 或 xlsx 文件", + "formatPreview": "数据格式预览", + "downloadTemplate": "下载模板", + "template": "模板", + "uploadFile": "上传文件", + "dropOrClick": "点击或拖拽文件到此处", + "supportedFormats": "支持 JSON、XLS、XLSX 格式", + "tags": "标签(可选)", + "tagsPlaceholder": "为导入的数据添加标签,多个标签用逗号分隔", + "tagsHelp": "导入的所有数据将打上这些标签", + "import": "导入", + "importing": "导入中...", + "failed": "导入失败", + "success": "导入成功", + "successMessage": "成功导入 {{count}} 条评估数据", + "showingErrors": "显示前 {{count}} 条错误", + "custom": "导入自定义数据集", + "builtin": "导入内置数据集", + "builtinTitle": "选择内置数据集", + "searchPlaceholder": "搜索数据集...", + "confirmImportTitle": "确认导入", + "confirmImportMessage": "确定要导入数据集 \"{{name}}\" 吗?这将添加新的评估数据到当前项目。", + "downloading": "下载中..." + }, + "export": { + "title": "导出评估数据集", + "formatLabel": "导出格式", + "filterLabel": "筛选条件", + "previewLabel": "将导出数据:", + "records": "条记录", + "largeDataHint": "数据量较大,将采用流式导出,请耐心等待", + "exporting": "导出中...", + "exportBtn": "导出", + "jsonDesc": "标准JSON数组", + "jsonlDesc": "每行一条记录", + "csvDesc": "表格格式", + "noTagsAvailable": "暂无可用标签" + } + }, + "evalTasks": { + "title": "模型评估任务", + "createTitle": "创建评估任务", + "detailTitle": "评估任务详情", + "createTask": "创建任务", + "noTasks": "暂无评估任务", + "noTasksHint": "创建评估任务来测试模型在评估数据集上的表现", + "selectModels": "选择测试模型", + "selectModelsHint": "可选择多个模型进行对比评估", + "selectJudgeModel": "选择教师模型", + "selectJudgeModelPlaceholder": "请选择...", + "selectJudgeModelHint": "教师模型用于评估简答题和开放题,不能与测试模型相同", + "judgeModel": "教师模型", + "filterByType": "按题型筛选", + "filterByTypeHint": "不选择则使用所有题目", + "selectedQuestions": "已选题目", + "questions": "道", + "hasSubjectiveHint": "包含主观题(简答题/开放题),需要选择教师模型进行评分", + "hasSubjective": "含主观题", + "startEval": "开始评估", + "progress": "进度", + "totalQuestions": "题目数量", + "status": "状态", + "totalScore": "总分", + "correctCount": "正确数", + "accuracy": "准确率", + "statsByType": "按题型统计", + "resultDetails": "评估结果详情", + "question": "题目", + "questionType": "题型", + "result": "结果", + "score": "得分", + "correctAnswer": "正确答案", + "modelAnswer": "模型回答", + "judgeResponse": "教师模型评分", + "interrupt": "中断任务", + "statusProcessing": "进行中", + "statusCompleted": "已完成", + "statusFailed": "失败", + "statusInterrupted": "已中断", + "deleteConfirmTitle": "确认删除", + "deleteConfirmMessage": "确定要删除这个评估任务吗?此操作将同时删除所有评估结果。", + "interruptConfirmTitle": "确认中断", + "interruptConfirmMessage": "确定要中断这个评估任务吗?已完成的评估结果将保留。", + "errorNoModels": "请至少选择一个测试模型", + "errorNoQuestions": "没有可用的评估题目", + "errorNoJudgeModel": "存在主观题,请选择一个教师模型用于评分", + "errorJudgeSameAsTest": "教师模型不能与测试模型相同", + "errorCreateFailed": "创建评估任务失败", + "errorLoadFailed": "加载评估任务失败", + "errorDeleteFailed": "删除评估任务失败", + "errorInterruptFailed": "中断评估任务失败", + "statusSuccess": "成功", + "statusFormatError": "格式错误", + "statusApiError": "API错误", + "statusUnknown": "未知状态", + "duration": "耗时", + "answerStatus": "答题状态", + "modelInfo": "模型信息", + "reportTitle": "模型能力评估报告", + "taskIdLabel": "任务 ID", + "pageInfo": "第 {{page}} / {{totalPages}} 页", + "noMatchingResults": "暂无符合条件的评估结果", + "reportFooter": "Easy Dataset Evaluation System · Generated by AI", + "finalSelection": "最终选择:", + "questionsSuffix": "道题目", + "noModelsAvailable": "暂无可用模型,请先在设置中配置模型", + "filterTitle": "题目筛选", + "clearFilter": "清空筛选", + "searchKeyword": "搜索关键字", + "searchPlaceholder": "搜索题目或答案内容...", + "filterByTypeLabel": "题型筛选", + "filterByTagLabel": "标签筛选", + "questionCountLabel": "题目数量:", + "useAllQuestions": "使用全部筛选结果", + "randomSampleHint": "将从 {{filteredCount}} 道题中随机抽取 {{questionCount}} 道", + "durationFormat": "(耗时 {{time}}s)", + "totalQuestionsLabel": "总题数", + "correctLabel": "正确", + "incorrectLabel": "错误", + "judgeComment": "AI 教师点评:", + "scoreUnit": "分", + "shortAnswer": "简答题", + "openEnded": "开放题", + "scoreAnchorsTitle": "{{type}}评分规则", + "customizable": "可自定义", + "scoreAnchorsHint": "自定义评分标准,用于指导LLM评估模型的回答质量", + "restoreDefault": "恢复默认", + "scoreRange": "分数区间", + "scoreDescriptionPlaceholder": "请输入该分数区间的评分标准描述..." + }, + "blindTest": { + "title": "人工盲测任务", + "createTitle": "创建盲测任务", + "createTask": "创建任务", + "noTasks": "暂无盲测任务", + "noTasksHint": "创建盲测任务来对比两个模型的回答质量", + "selectModels": "选择对比模型", + "modelA": "模型 A", + "modelB": "模型 B", + "modelComparison": "模型对比", + "selectQuestions": "选择测试题目", + "questionType": "题型", + "questionTypeHint": "盲测任务仅支持简答题和开放题", + "filterByTag": "按标签筛选", + "questionCount": "题目数量", + "availableQuestions": "可用题目:{{count}} 道", + "useAllQuestions": "使用全部筛选结果", + "randomSample": "将随机抽取 {{count}} 道题目", + "startBlindTest": "开始盲测", + "creating": "创建中...", + "noModelsAvailable": "暂无可用模型,请先在设置中配置模型", + "errorSelectModelA": "请选择模型A", + "errorSelectModelB": "请选择模型B", + "errorSameModel": "两个模型不能相同", + "errorNoQuestions": "没有符合条件的题目", + "statusProcessing": "进行中", + "statusCompleted": "已完成", + "statusFailed": "失败", + "statusInterrupted": "已中断", + "progress": "进度", + "viewDetails": "查看详情", + "continue": "继续盲测", + "interrupt": "中断任务", + "deleteConfirmTitle": "确认删除", + "deleteConfirmMessage": "确定要删除这个盲测任务吗?此操作不可撤销。", + "interruptConfirmTitle": "确认中断", + "interruptConfirmMessage": "确定要中断这个盲测任务吗?已完成的评判结果将保留。", + "inProgress": "盲测进行中", + "generatingAnswers": "正在生成回答...", + "question": "问题", + "answerA": "回答 A", + "answerB": "回答 B", + "duration": "耗时", + "whichBetter": "哪个回答更好?", + "leftBetter": "左边更好", + "rightBetter": "右边更好", + "bothGood": "都好", + "bothBad": "都不好", + "loadQuestion": "加载题目", + "taskNotFound": "任务不存在", + "resultTitle": "盲测结果", + "resultSummary": "评测结果汇总", + "wins": "胜出", + "times": "次", + "totalQuestions": "总题数", + "ties": "平局", + "detailResults": "详细结果", + "left": "左", + "right": "右" + } +} diff --git a/easy-dataset-main/next.config.js b/easy-dataset-main/next.config.js new file mode 100644 index 0000000..37f271f --- /dev/null +++ b/easy-dataset-main/next.config.js @@ -0,0 +1,19 @@ +// 最佳实践配置示例 +module.exports = { + experimental: { + serverComponentsExternalPackages: ['@opendocsg/pdf2md', 'pdfjs-dist', '@hyzyla/pdfium'], + esmExternals: 'loose' + }, + webpack: (config, { isServer }) => { + if (!isServer) { + config.externals.push({ + unpdf: 'window.unpdf', + 'pdfjs-dist': 'window.pdfjsLib' + }); + } else { + config.externals.push('pdfjs-dist'); + config.externals.push('@hyzyla/pdfium'); + } + return config; + } +}; diff --git a/easy-dataset-main/package-lock.json b/easy-dataset-main/package-lock.json new file mode 100644 index 0000000..45b89a7 --- /dev/null +++ b/easy-dataset-main/package-lock.json @@ -0,0 +1,18891 @@ +{ + "name": "easy-dataset", + "version": "1.7.2", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "easy-dataset", + "version": "1.7.2", + "license": "AGPL 3.0", + "dependencies": { + "@ai-sdk/openai": "^1.3.9", + "@ai-sdk/openai-compatible": "^1.0.22", + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", + "@fontsource/inter": "^5.0.16", + "@fontsource/jetbrains-mono": "^5.0.18", + "@huggingface/hub": "^2.0.2", + "@lobehub/icons": "^1.96.0", + "@mui/icons-material": "5.16.14", + "@mui/lab": "5.0.0-alpha.175", + "@mui/material": "5.16.14", + "@opendocsg/pdf2md": "^0.2.1", + "@openrouter/ai-sdk-provider": "^0.4.5", + "@prisma/client": "^6.6.0", + "adm-zip": "^0.5.16", + "ai": "^4.3.4", + "axios": "^1.8.4", + "electron-build": "^0.0.3", + "electron-updater": "^6.3.9", + "formidable": "^3.5.2", + "framer-motion": "^12.4.10", + "github-markdown-css": "^5.8.1", + "i18next": "^24.2.2", + "i18next-browser-languagedetector": "^8.0.4", + "image-size": "^2.0.2", + "jotai": "^2.12.3", + "jsonrepair": "^3.13.1", + "jszip": "^3.10.1", + "langchain": "^0.3.24", + "mammoth": "^1.9.0", + "nanoid": "^5.1.5", + "next": "^14.2.29", + "next-themes": "^0.2.1", + "ollama-ai-provider": "^1.2.0", + "opener": "^1.5.2", + "pdf2md-js": "1.0.8", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^15.4.1", + "react-markdown": "^10.0.1", + "recharts": "^3.6.0", + "sharp": "^0.33.1", + "sonner": "^2.0.3", + "turndown": "^7.2.0", + "xlsx": "^0.18.5", + "xmldom": "^0.6.0", + "zhipu-ai-provider": "^0.1.1", + "zod": "^3.25.76" + }, + "bin": { + "easy-dataset": "desktop/server.js" + }, + "devDependencies": { + "@commitlint/cli": "^19.8.0", + "@commitlint/config-conventional": "^19.8.0", + "concurrently": "^8.2.2", + "electron": "^35.0.0", + "electron-builder": "^24.13.3", + "husky": "^9.1.7", + "lint-staged": "15.5.2", + "pkg": "^5.8.1", + "prisma": "^6.6.0", + "wait-on": "^7.2.0" + } + }, + "node_modules/@ai-sdk/openai": { + "version": "1.3.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-1.3.22.tgz", + "integrity": "sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@ai-sdk/openai-compatible": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/@ai-sdk/openai-compatible/-/openai-compatible-1.0.22.tgz", + "integrity": "sha512-Q+lwBIeMprc/iM+vg1yGjvzRrp74l316wDpqWdbmd4VXXlllblzGsUgBLTeKvcEapFTgqk0FRETvSb58Y6dsfA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@ai-sdk/provider-utils": "3.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.0.tgz", + "integrity": "sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/openai-compatible/node_modules/@ai-sdk/provider-utils": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.12.tgz", + "integrity": "sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "2.0.0", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.1.3.tgz", + "integrity": "sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.2.8.tgz", + "integrity": "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@ai-sdk/provider-utils/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/@ai-sdk/react": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-1.2.12.tgz", + "integrity": "sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/ui-utils": "1.2.11", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@ai-sdk/ui-utils": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-1.2.11.tgz", + "integrity": "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@ant-design/colors": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.2.1.tgz", + "integrity": "sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/fast-color": "^2.0.6" + } + }, + "node_modules/@ant-design/cssinjs": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.23.0.tgz", + "integrity": "sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "@emotion/hash": "^0.8.0", + "@emotion/unitless": "^0.7.5", + "classnames": "^2.3.1", + "csstype": "^3.1.3", + "rc-util": "^5.35.0", + "stylis": "^4.3.4" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.1.3.tgz", + "integrity": "sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/cssinjs/node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/@ant-design/cssinjs/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/@ant-design/cssinjs/node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@ant-design/icons": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.6.1.tgz", + "integrity": "sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^7.0.0", + "@ant-design/icons-svg": "^4.4.0", + "@babel/runtime": "^7.24.8", + "classnames": "^2.2.6", + "rc-util": "^5.31.1" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/@ant-design/icons-svg": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.4.2.tgz", + "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==", + "license": "MIT", + "peer": true + }, + "node_modules/@ant-design/react-slick": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.1.2.tgz", + "integrity": "sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.4", + "classnames": "^2.2.5", + "json2mq": "^0.2.0", + "resize-observer-polyfill": "^1.5.1", + "throttle-debounce": "^5.0.0" + }, + "peerDependencies": { + "react": ">=16.9.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/utils": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-8.1.1.tgz", + "integrity": "sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/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==", + "license": "MIT", + "peer": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-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==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", + "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "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.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz", + "integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.11.0", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/runtime": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", + "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", + "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "license": "MIT" + }, + "node_modules/@cfworker/json-schema": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz", + "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==", + "license": "MIT", + "peer": true + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, + "node_modules/@commitlint/cli": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-19.8.1.tgz", + "integrity": "sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/format": "^19.8.1", + "@commitlint/lint": "^19.8.1", + "@commitlint/load": "^19.8.1", + "@commitlint/read": "^19.8.1", + "@commitlint/types": "^19.8.1", + "tinyexec": "^1.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "commitlint": "cli.js" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-conventional": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-19.8.1.tgz", + "integrity": "sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-conventionalcommits": "^7.0.2" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/config-validator": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-19.8.1.tgz", + "integrity": "sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "ajv": "^8.11.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/ensure": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-19.8.1.tgz", + "integrity": "sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "lodash.camelcase": "^4.3.0", + "lodash.kebabcase": "^4.1.1", + "lodash.snakecase": "^4.1.1", + "lodash.startcase": "^4.4.0", + "lodash.upperfirst": "^4.3.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/execute-rule": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-19.8.1.tgz", + "integrity": "sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/format": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-19.8.1.tgz", + "integrity": "sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/is-ignored": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-19.8.1.tgz", + "integrity": "sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "semver": "^7.6.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/lint": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-19.8.1.tgz", + "integrity": "sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/is-ignored": "^19.8.1", + "@commitlint/parse": "^19.8.1", + "@commitlint/rules": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/load": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-19.8.1.tgz", + "integrity": "sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/execute-rule": "^19.8.1", + "@commitlint/resolve-extends": "^19.8.1", + "@commitlint/types": "^19.8.1", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", + "cosmiconfig-typescript-loader": "^6.1.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/message": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-19.8.1.tgz", + "integrity": "sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/parse": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-19.8.1.tgz", + "integrity": "sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/types": "^19.8.1", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/read": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-19.8.1.tgz", + "integrity": "sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/top-level": "^19.8.1", + "@commitlint/types": "^19.8.1", + "git-raw-commits": "^4.0.0", + "minimist": "^1.2.8", + "tinyexec": "^1.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/resolve-extends": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-19.8.1.tgz", + "integrity": "sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/config-validator": "^19.8.1", + "@commitlint/types": "^19.8.1", + "global-directory": "^4.0.1", + "import-meta-resolve": "^4.0.0", + "lodash.mergewith": "^4.6.2", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/rules": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-19.8.1.tgz", + "integrity": "sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@commitlint/ensure": "^19.8.1", + "@commitlint/message": "^19.8.1", + "@commitlint/to-lines": "^19.8.1", + "@commitlint/types": "^19.8.1" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/to-lines": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-19.8.1.tgz", + "integrity": "sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/top-level": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-19.8.1.tgz", + "integrity": "sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^7.0.0" + }, + "engines": { + "node": ">=v18" + } + }, + "node_modules/@commitlint/types": { + "version": "19.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-19.8.1.tgz", + "integrity": "sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" + }, + "engines": { + "node": ">=v18" + } + }, + "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/@develar/schema-utils/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/@develar/schema-utils/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/@develar/schema-utils/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/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/modifiers": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/modifiers/-/modifiers-9.0.0.tgz", + "integrity": "sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.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/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/@electron/asar/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/@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/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/@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/notarize/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/notarize/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/@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/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/@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/osx-sign/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/osx-sign/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/@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/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/@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/@electron/universal/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/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/@electron/universal/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/@emnapi/runtime": { + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-0.44.0.tgz", + "integrity": "sha512-ZX/etZEZw8DR7zAB1eVQT40lNo0jeqpb6dCgOvctB6FIQ5PoXfMuNY8+ayQfu8tNQbAB8gQWSSJupR8NxeiZXw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emoji-mart/data": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz", + "integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==", + "license": "MIT" + }, + "node_modules/@emoji-mart/react": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/react/-/react-1.1.1.tgz", + "integrity": "sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==", + "license": "MIT", + "peerDependencies": { + "emoji-mart": "^5.2", + "react": "^16.8 || ^17 || ^18" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/css": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", + "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", + "dependencies": { + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" + }, + "node_modules/@emotion/styled": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", + "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" + }, + "node_modules/@floating-ui/core": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.1.tgz", + "integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.1.tgz", + "integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.1", + "@floating-ui/utils": "^0.2.9" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.27.12", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.12.tgz", + "integrity": "sha512-kKlWNrpIQxF1B/a2MZvE0/uyKby4960yjO91W7nVyNKmmfNi62xU9HCjL1M1eWzx/LFj/VPSwJVbwQk9Pq/68A==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.3", + "@floating-ui/utils": "^0.2.9", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.3.tgz", + "integrity": "sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", + "license": "MIT" + }, + "node_modules/@fontsource/inter": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.6.tgz", + "integrity": "sha512-CZs9S1CrjD0jPwsNy9W6j0BhsmRSQrgwlTNkgQXTsAeDRM42LBRLo3eo9gCzfH4GvV7zpyf78Ozfl773826csw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource/jetbrains-mono": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.6.tgz", + "integrity": "sha512-nz//dBr99hXZmHp10wgNI00qThWImkzRR5PQjvRM+rpmuHO5rYBJCqPPWufidCvmkkryXx/GOP/lgqsM3R3Org==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@giscus/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@giscus/react/-/react-3.1.0.tgz", + "integrity": "sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg==", + "dependencies": { + "giscus": "^1.6.0" + }, + "peerDependencies": { + "react": "^16 || ^17 || ^18 || ^19", + "react-dom": "^16 || ^17 || ^18 || ^19" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@huggingface/hub": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@huggingface/hub/-/hub-2.2.0.tgz", + "integrity": "sha512-G+VS1eMp80KovIHBlsiEigS6I6qmI4j+VQ1UZ8CaXT+pw2A7tj6e/crfxFdKNE2uOK5oQkRFiCBJykMwrWQ8OA==", + "license": "MIT", + "dependencies": { + "@huggingface/tasks": "^0.19.11" + }, + "bin": { + "hfjs": "dist/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@huggingface/tasks": { + "version": "0.19.14", + "resolved": "https://registry.npmjs.org/@huggingface/tasks/-/tasks-0.19.14.tgz", + "integrity": "sha512-TlyCOsUc+7y/uILTF8z61JzqBKfK50hIs0nDNmp+FiwXfQt4fR7Z0eUduq5JUm8uK5vL/l5pn9bjmXnDawYUOQ==", + "license": "MIT" + }, + "node_modules/@hyzyla/pdfium": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@hyzyla/pdfium/-/pdfium-2.1.7.tgz", + "integrity": "sha512-GQ0mxuVY15RHAyxVRC8IUREAeuhZ+Efab8czhvwiw3mveNmBQAtaV3x2O70NaQlu5juzaP0DXK3Jq8bh6Jgjjw==", + "license": "MIT" + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.3.0.tgz", + "integrity": "sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.0.0", + "@antfu/utils": "^8.1.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.0", + "globals": "^15.14.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.0.0", + "mlly": "^1.7.4" + } + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.1.tgz", + "integrity": "sha512-esr2BZ1x0bo+wl7Gx2hjssYhjrhUsD88VQulI0FrG8/otRQUOxLWHMBd1Y1qo2Gfg2KUvXNpT0ASnV9BzJCexw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.0" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.1.tgz", + "integrity": "sha512-YrnuB3bXuWdG+hJlXtq7C73lF8ampkhU3tMxg5Hh+E7ikxbUVOU9nlNtVTloDXz6pRHt2y2oKJq7DY/yt+UXYw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.0" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.0.tgz", + "integrity": "sha512-VzYd6OwnUR81sInf3alj1wiokY50DjsHz5bvfnsFpxs5tqQxESoHtJO6xyksDs3RIkyhMWq2FufXo6GNSU9BMw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=11", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.0.tgz", + "integrity": "sha512-dD9OznTlHD6aovRswaPNEy8dKtSAmNo4++tO7uuR4o5VxbVAOoEQ1uSmN4iFAdQneTHws1lkTZeiXPrcCkh6IA==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "macos": ">=10.13", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.0.tgz", + "integrity": "sha512-VwgD2eEikDJUk09Mn9Dzi1OW2OJFRQK+XlBTkUNmAWPrtj8Ly0yq05DFgu1VCMx2/DqCGQVi5A1dM9hTmxf3uw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.0.tgz", + "integrity": "sha512-xTYThiqEZEZc0PRU90yVtM3KE7lw1bKdnDQ9kCTHWbqWyHOe4NpPOtMGy27YnN51q0J5dqRrvicfPbALIOeAZA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.0.tgz", + "integrity": "sha512-o9E46WWBC6JsBlwU4QyU9578G77HBDT1NInd+aERfxeOPbk0qBZHgoDsQmA2v9TbqJRWzoBPx1aLOhprBMgPjw==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.0.tgz", + "integrity": "sha512-naldaJy4hSVhWBgEjfdBY85CAa4UO+W1nx6a1sWStHZ7EUfNiuBTTN2KUYT5dH1+p/xij1t2QSXfCiFJoC5S/Q==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.0.tgz", + "integrity": "sha512-OdorplCyvmSAPsoJLldtLh3nLxRrkAAAOHsGWGDYfN0kh730gifK+UZb3dWORRa6EusNqCTjfXV4GxvgJ/nPDQ==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.0.tgz", + "integrity": "sha512-FW8iK6rJrg+X2jKD0Ajhjv6y74lToIBEvkZhl42nZt563FfxkCYacrXZtd+q/sRQDypQLzY5WdLkVTbJoPyqNg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.1.tgz", + "integrity": "sha512-Ii4X1vnzzI4j0+cucsrYA5ctrzU9ciXERfJR633S2r39CiD8npqH2GMj63uFZRCFt3E687IenAdbwIpQOJ5BNA==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.0" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.1.tgz", + "integrity": "sha512-59B5GRO2d5N3tIfeGHAbJps7cLpuWEQv/8ySd9109ohQ3kzyCACENkFVAnGPX00HwPTQcaBNF7HQYEfZyZUFfw==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.0" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.1.tgz", + "integrity": "sha512-tRGrb2pHnFUXpOAj84orYNxHADBDIr0J7rrjwQrTNMQMWA4zy3StKmMvwsI7u3dEZcgwuMMooIIGWEWOjnmG8A==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.28", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.0" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.1.tgz", + "integrity": "sha512-4y8osC0cAc1TRpy02yn5omBeloZZwS62fPZ0WUAYQiLhSFSpWJfY/gMrzKzLcHB9ulUV6ExFiu2elMaixKDbeg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "glibc": ">=2.26", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.0" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.1.tgz", + "integrity": "sha512-D3lV6clkqIKUizNS8K6pkuCKNGmWoKlBGh5p0sLO2jQERzbakhu4bVX1Gz+RS4vTZBprKlWaf+/Rdp3ni2jLfA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.0" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.1.tgz", + "integrity": "sha512-LOGKNu5w8uu1evVqUAUKTix2sQu1XDRIYbsi5Q0c/SrXhvJ4QyOx+GaajxmOg5PZSsSnCYPSmhjHHsRBx06/wQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "musl": ">=1.2.2", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.0" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.1.tgz", + "integrity": "sha512-vWI/sA+0p+92DLkpAMb5T6I8dg4z2vzCUnp8yvxHlwBpzN8CIcO3xlSXrLltSvK6iMsVMNswAv+ub77rsf25lA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^0.44.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.1.tgz", + "integrity": "sha512-/xhYkylsKL05R+NXGJc9xr2Tuw6WIVl2lubFJaFYfW4/MQ4J+dgjIo/T4qjNRizrqs/szF/lC9a5+updmY9jaQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.1.tgz", + "integrity": "sha512-XaM69X0n6kTEsp9tVYYLhXdg7Qj32vYJlAKRutxUsm1UlgQNx6BOhHwZPwukCGXBU2+tH87ip2eV1I/E8MQnZg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0", + "npm": ">=9.6.5", + "pnpm": ">=7.1.0", + "yarn": ">=3.2.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "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==", + "dev": true, + "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-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "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.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@langchain/core": { + "version": "0.3.57", + "resolved": "https://registry.npmjs.org/@langchain/core/-/core-0.3.57.tgz", + "integrity": "sha512-jz28qCTKJmi47b6jqhQ6vYRTG5jRpqhtPQjriRTB5wR8mgvzo6xKs0fG/kExS3ZvM79ytD1npBvgf8i19xOo9Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@cfworker/json-schema": "^4.0.2", + "ansi-styles": "^5.0.0", + "camelcase": "6", + "decamelize": "1.2.0", + "js-tiktoken": "^1.0.12", + "langsmith": "^0.3.29", + "mustache": "^4.2.0", + "p-queue": "^6.6.2", + "p-retry": "4", + "uuid": "^10.0.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@langchain/openai": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/@langchain/openai/-/openai-0.5.12.tgz", + "integrity": "sha512-k7rxBY3ed/HIiMLd6HBqFibsfB0+L6c82H8JgXDqKeyUoACJIi1JaKHXmofmCeF2SBXBU9dog4gEGpHfcUDGUA==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12", + "openai": "^4.96.0", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.3.48 <0.4.0" + } + }, + "node_modules/@langchain/textsplitters": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@langchain/textsplitters/-/textsplitters-0.1.0.tgz", + "integrity": "sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw==", + "license": "MIT", + "dependencies": { + "js-tiktoken": "^1.0.12" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/core": ">=0.2.21 <0.4.0" + } + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz", + "integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.0.tgz", + "integrity": "sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, + "node_modules/@lobehub/emojilib": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lobehub/emojilib/-/emojilib-1.0.0.tgz", + "integrity": "sha512-s9KnjaPjsEefaNv150G3aifvB+J3P4eEKG+epY9zDPS2BeB6+V2jELWqAZll+nkogMaVovjEE813z3V751QwGw==", + "license": "MIT" + }, + "node_modules/@lobehub/fluent-emoji": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lobehub/fluent-emoji/-/fluent-emoji-1.2.0.tgz", + "integrity": "sha512-l2Ed3kyNQpiquRVROYO/frFifuuGoXizkC7e93UIJ9lRLnwLx2XKi0EqGse0Qld/BHsfJfrlXhbwNBYd2RDZqQ==", + "license": "MIT", + "dependencies": { + "@lobehub/emojilib": "^1.0.0", + "@lobehub/ui": "^1.161.0", + "antd-style": "^3.7.1", + "emoji-regex": "^10.4.0", + "lodash-es": "^4.17.21", + "lucide-react": "^0.469.0", + "react-layout-kit": "^1.9.1", + "url-join": "^5.0.0" + }, + "peerDependencies": { + "antd": "^5.23.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@lobehub/icons": { + "version": "1.97.2", + "resolved": "https://registry.npmjs.org/@lobehub/icons/-/icons-1.97.2.tgz", + "integrity": "sha512-b6aGDVc+mgyyvfmhclX0XvH1vcaXb7oG6HQ8oGFaeU1CHghI/suiG40YQOWYYt5u8vqyVVbZVKm+i+KEp75j6A==", + "license": "MIT", + "dependencies": { + "@lobehub/ui": "^1.168.7", + "antd-style": "^3.7.1", + "lucide-react": "^0.469.0", + "polished": "^4.3.1", + "react-layout-kit": "^1.9.1" + }, + "peerDependencies": { + "antd": "^5.23.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@lobehub/ui": { + "version": "1.171.0", + "resolved": "https://registry.npmjs.org/@lobehub/ui/-/ui-1.171.0.tgz", + "integrity": "sha512-MP1AUHmV15lCcxps7Ncw9QEeWF4Zlj/o1Lvi0MoEWVsdE1myv6I/DRlXcyJLKXgU/FKrMX6EAcFrE+NwlXUtfw==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.23.0", + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/modifiers": "^9.0.0", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", + "@emoji-mart/data": "^1.2.1", + "@emoji-mart/react": "^1.1.1", + "@floating-ui/react": "^0.27.5", + "@giscus/react": "^3.1.0", + "@lobehub/fluent-emoji": "^1.2.0", + "@lobehub/icons": "^1.94.0", + "@mdx-js/mdx": "^3.1.0", + "@mdx-js/react": "^3.1.0", + "@radix-ui/react-slot": "^1.1.2", + "@shikijs/transformers": "^3.2.1", + "@splinetool/runtime": "0.9.526", + "ahooks": "^3.8.4", + "antd-style": "^3.7.1", + "chroma-js": "^3.1.2", + "class-variance-authority": "^0.7.1", + "dayjs": "^1.11.13", + "emoji-mart": "^5.6.0", + "fast-deep-equal": "^3.1.3", + "framer-motion": "^11.18.2", + "immer": "^10.1.1", + "leva": "^0.10.0", + "lodash-es": "^4.17.21", + "lucide-react": "^0.484.0", + "mermaid": "^11.6.0", + "numeral": "^2.0.6", + "polished": "^4.3.1", + "query-string": "^9.1.1", + "rc-footer": "^0.6.8", + "re-resizable": "^6.11.2", + "react-avatar-editor": "^13.0.2", + "react-error-boundary": "^5.0.0", + "react-hotkeys-hook": "^4.6.1", + "react-layout-kit": "^1.9.1", + "react-markdown": "^10.1.0", + "react-merge-refs": "^2.1.1", + "react-rnd": "^10.5.2", + "react-zoom-pan-pinch": "^3.7.0", + "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", + "remark-breaks": "^4.0.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "shiki": "^3.2.1", + "swr": "^2.3.3", + "ts-md5": "^1.3.1", + "unified": "^11.0.5", + "url-join": "^5.0.0", + "use-merge-value": "^1.2.0", + "uuid": "^11.1.0" + }, + "peerDependencies": { + "antd": "^5.23.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@lobehub/ui/node_modules/framer-motion": { + "version": "11.18.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz", + "integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.18.1", + "motion-utils": "^11.18.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/@lobehub/ui/node_modules/lucide-react": { + "version": "0.484.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.484.0.tgz", + "integrity": "sha512-oZy8coK9kZzvqhSgfbGkPtTgyjpBvs3ukLgDPv14dSOZtBtboryWF5o8i3qen7QbGg7JhiJBz5mK1p8YoMZTLQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@lobehub/ui/node_modules/motion-dom": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz", + "integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^11.18.1" + } + }, + "node_modules/@lobehub/ui/node_modules/motion-utils": { + "version": "11.18.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz", + "integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==", + "license": "MIT" + }, + "node_modules/@lobehub/ui/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "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/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/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/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mdx-js/mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.0.tgz", + "integrity": "sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdx": "^2.0.0", + "collapse-white-space": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-util-scope": "^1.0.0", + "estree-walker": "^3.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "markdown-extensions": "^2.0.0", + "recma-build-jsx": "^1.0.0", + "recma-jsx": "^1.0.0", + "recma-stringify": "^1.0.0", + "rehype-recma": "^1.0.0", + "remark-mdx": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "source-map": "^0.7.0", + "unified": "^11.0.0", + "unist-util-position-from-estree": "^2.0.0", + "unist-util-stringify-position": "^4.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/@mdx-js/mdx/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@mdx-js/react": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.0.tgz", + "integrity": "sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==", + "license": "MIT", + "dependencies": { + "@types/mdx": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=16", + "react": ">=16" + } + }, + "node_modules/@mermaid-js/parser": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.4.0.tgz", + "integrity": "sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==", + "license": "MIT", + "dependencies": { + "langium": "3.3.1" + } + }, + "node_modules/@mixmark-io/domino": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz", + "integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==", + "license": "BSD-2-Clause" + }, + "node_modules/@mui/base": { + "version": "5.0.0-beta.40-0", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.40-0.tgz", + "integrity": "sha512-hG3atoDUxlvEy+0mqdMpWd04wca8HKr2IHjW/fAjlkCHQolSLazhZM46vnHjOf15M4ESu25mV/3PgjczyjVM4w==", + "deprecated": "This package has been replaced by @base-ui-components/react", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@floating-ui/react-dom": "^2.0.8", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.12", + "@popperjs/core": "^2.11.8", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.17.1.tgz", + "integrity": "sha512-OcZj+cs6EfUD39IoPBOgN61zf1XFVY+imsGoBDwXeSq2UHJZE3N59zzBOVjclck91Ne3e9gudONOeILvHCIhUA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.16.14.tgz", + "integrity": "sha512-heL4S+EawrP61xMXBm59QH6HODsu0gxtZi5JtnXF2r+rghzyU/3Uftlt1ij8rmJh+cFdKTQug1L9KkZB5JgpMQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^5.0.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/lab": { + "version": "5.0.0-alpha.175", + "resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.175.tgz", + "integrity": "sha512-AvM0Nvnnj7vHc9+pkkQkoE1i+dEbr6gsMdnSfy7X4w3Ljgcj1yrjZhIt3jGTCLzyKVLa6uve5eLluOcGkvMqUA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/base": "5.0.0-beta.40-0", + "@mui/system": "^5.16.12", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.12", + "clsx": "^2.1.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material": ">=5.15.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.14.tgz", + "integrity": "sha512-eSXQVCMKU2xc7EcTxe/X/rC9QsV2jUe8eLM3MUCPYbo6V52eCE436akRIvELq/AqZpxx2bwkq7HC0cRhLB+yaw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/core-downloads-tracker": "^5.16.14", + "@mui/system": "^5.16.14", + "@mui/types": "^7.2.15", + "@mui/utils": "^5.16.14", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.10", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^19.0.0", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz", + "integrity": "sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/utils": "^5.17.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "5.16.14", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.16.14.tgz", + "integrity": "sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@emotion/cache": "^11.13.5", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.17.1.tgz", + "integrity": "sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/private-theming": "^5.17.1", + "@mui/styled-engine": "^5.16.14", + "@mui/types": "~7.2.15", + "@mui/utils": "^5.17.1", + "clsx": "^2.1.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/system/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.3.tgz", + "integrity": "sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.27.1" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.17.1.tgz", + "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "@mui/types": "~7.2.15", + "@types/prop-types": "^15.7.12", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils/node_modules/@mui/types": { + "version": "7.2.24", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", + "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@next/env": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.29.tgz", + "integrity": "sha512-UzgLR2eBfhKIQt0aJ7PWH7XRPYw7SXz0Fpzdl5THjUnvxy4kfBk9OU4RNPNiETewEEtaBcExNFNn1QWH8wQTjg==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.29.tgz", + "integrity": "sha512-wWtrAaxCVMejxPHFb1SK/PVV1WDIrXGs9ki0C/kUM8ubKHQm+3hU9MouUywCw8Wbhj3pewfHT2wjunLEr/TaLA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.29.tgz", + "integrity": "sha512-7Z/jk+6EVBj4pNLw/JQrvZVrAh9Bv8q81zCFSfvTMZ51WySyEHWVpwCEaJY910LyBftv2F37kuDPQm0w9CEXyg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.29.tgz", + "integrity": "sha512-o6hrz5xRBwi+G7JFTHc+RUsXo2lVXEfwh4/qsuWBMQq6aut+0w98WEnoNwAwt7hkEqegzvazf81dNiwo7KjITw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.29.tgz", + "integrity": "sha512-9i+JEHBOVgqxQ92HHRFlSW1EQXqa/89IVjtHgOqsShCcB/ZBjTtkWGi+SGCJaYyWkr/lzu51NTMCfKuBf7ULNw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.29.tgz", + "integrity": "sha512-B7JtMbkUwHijrGBOhgSQu2ncbCYq9E7PZ7MX58kxheiEOwdkM+jGx0cBb+rN5AeqF96JypEppK6i/bEL9T13lA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.29.tgz", + "integrity": "sha512-yCcZo1OrO3aQ38B5zctqKU1Z3klOohIxug6qdiKO3Q3qNye/1n6XIs01YJ+Uf+TdpZQ0fNrOQI2HrTLF3Zprnw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.29.tgz", + "integrity": "sha512-WnrfeOEtTVidI9Z6jDLy+gxrpDcEJtZva54LYC0bSKQqmyuHzl0ego+v0F/v2aXq0am67BRqo/ybmmt45Tzo4A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-ia32-msvc": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.29.tgz", + "integrity": "sha512-vkcriFROT4wsTdSeIzbxaZjTNTFKjSYmLd8q/GVH3Dn8JmYjUKOuKXHK8n+lovW/kdcpIvydO5GtN+It2CvKWA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.29.tgz", + "integrity": "sha512-iPPwUEKnVs7pwR0EBLJlwxLD7TTHWS/AoVZx1l9ZQzfQciqaFEr5AlYzA2uB6Fyby1IF18t4PL0nTpB+k4Tzlw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opendocsg/pdf2md": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@opendocsg/pdf2md/-/pdf2md-0.2.1.tgz", + "integrity": "sha512-k/yvfrTb+GPTIIm/bMm5IsenTqAFl+IqvkBgFwFlmflS5TT7FOfyRLp8MypVWLAG4G9AnT7AZFbdQYgN/CR5BA==", + "license": "MIT", + "dependencies": { + "enumify": "^1.0.4", + "minimist": "^1.2.5", + "unpdf": "^0.12.1" + }, + "bin": { + "pdf2md": "lib/pdf2md-cli.js" + } + }, + "node_modules/@openrouter/ai-sdk-provider": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@openrouter/ai-sdk-provider/-/ai-sdk-provider-0.4.6.tgz", + "integrity": "sha512-oUa8xtssyUhiKEU/aW662lsZ0HUvIUTRk8vVIF3Ha3KI/DnqX54zmVIuzYnaDpermqhy18CHqblAY4dDt1JW3g==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.9", + "@ai-sdk/provider-utils": "2.1.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/@openrouter/ai-sdk-provider/node_modules/@ai-sdk/provider": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.0.9.tgz", + "integrity": "sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@openrouter/ai-sdk-provider/node_modules/@ai-sdk/provider-utils": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.1.10.tgz", + "integrity": "sha512-4GZ8GHjOFxePFzkl3q42AU0DQOtTQ5w09vmaWUf/pKFXJPizlnzKSUkF0f+VkapIUfDugyMqPMT1ge8XQzVI7Q==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.9", + "eventsource-parser": "^3.0.0", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@openrouter/ai-sdk-provider/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/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "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==", + "dev": true, + "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/@prisma/client": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.9.0.tgz", + "integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@prisma/config": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.9.0.tgz", + "integrity": "sha512-Wcfk8/lN3WRJd5w4jmNQkUwhUw0eksaU/+BlAJwPQKW10k0h0LC9PD/6TQFmqKVbHQL0vG2z266r0S1MPzzhbA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "jiti": "2.4.2" + } + }, + "node_modules/@prisma/debug": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.9.0.tgz", + "integrity": "sha512-bFeur/qi/Q+Mqk4JdQ3R38upSYPebv5aOyD1RKywVD+rAMLtRkmTFn28ZuTtVOnZHEdtxnNOCH+bPIeSGz1+Fg==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/engines": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.9.0.tgz", + "integrity": "sha512-im0X0bwDLA0244CDf8fuvnLuCQcBBdAGgr+ByvGfQY9wWl6EA+kRGwVk8ZIpG65rnlOwtaWIr/ZcEU5pNVvq9g==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.9.0", + "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "@prisma/fetch-engine": "6.9.0", + "@prisma/get-platform": "6.9.0" + } + }, + "node_modules/@prisma/engines-version": { + "version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e.tgz", + "integrity": "sha512-Qp9gMoBHgqhKlrvumZWujmuD7q4DV/gooEyPCLtbkc13EZdSz2RsGUJ5mHb3RJgAbk+dm6XenqG7obJEhXcJ6Q==", + "devOptional": true, + "license": "Apache-2.0" + }, + "node_modules/@prisma/fetch-engine": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.9.0.tgz", + "integrity": "sha512-PMKhJdl4fOdeE3J3NkcWZ+tf3W6rx3ht/rLU8w4SXFRcLhd5+3VcqY4Kslpdm8osca4ej3gTfB3+cSk5pGxgFg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.9.0", + "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "@prisma/get-platform": "6.9.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.9.0.tgz", + "integrity": "sha512-/B4n+5V1LI/1JQcHp+sUpyRT1bBgZVPHbsC4lt4/19Xp4jvNIVcq5KYNtQDk5e/ukTSjo9PZVAxxy9ieFtlpTQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/debug": "6.9.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", + "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.2.tgz", + "integrity": "sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.2" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz", + "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.3.tgz", + "integrity": "sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-escape-keydown": "1.0.2" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", + "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.1.tgz", + "integrity": "sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "0.7.2", + "@radix-ui/react-arrow": "1.0.2", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0", + "@radix-ui/react-use-rect": "1.0.0", + "@radix-ui/react-use-size": "1.0.0", + "@radix-ui/rect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/core": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", + "integrity": "sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/dom": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-0.5.4.tgz", + "integrity": "sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^0.7.3" + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@floating-ui/react-dom": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-0.7.2.tgz", + "integrity": "sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^0.5.3", + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.2.tgz", + "integrity": "sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.2" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", + "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-presence/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.2.tgz", + "integrity": "sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.1" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.5.tgz", + "integrity": "sha512-cDKVcfzyO6PpckZekODJZDe5ZxZ2fCZlzKzTmPhe4mX9qTHRfLcKgqb0OKf22xLwDequ2tVleim+ZYx3rabD5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.3", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-popper": "1.1.1", + "@radix-ui/react-portal": "1.0.2", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.2", + "@radix-ui/react-slot": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.0", + "@radix-ui/react-visually-hidden": "1.0.2" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.1.tgz", + "integrity": "sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", + "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz", + "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.2.tgz", + "integrity": "sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", + "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.0.tgz", + "integrity": "sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.0.tgz", + "integrity": "sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.2.tgz", + "integrity": "sha512-qirnJxtYn73HEk1rXL12/mXnu2rwsNHDID10th2JGtdK25T9wX+mxRmGt7iPSahw512GbZOc0syZX1nLQGoEOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.2" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.0.tgz", + "integrity": "sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@rc-component/async-validator": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", + "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.4" + }, + "engines": { + "node": ">=14.x" + } + }, + "node_modules/@rc-component/color-picker": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/fast-color": "^2.0.6", + "@babel/runtime": "^7.23.6", + "classnames": "^2.2.6", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/context": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@rc-component/context/-/context-1.4.0.tgz", + "integrity": "sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/mini-decimal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mini-decimal/-/mini-decimal-1.1.0.tgz", + "integrity": "sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0" + }, + "engines": { + "node": ">=8.x" + } + }, + "node_modules/@rc-component/mutate-observer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/mutate-observer/-/mutate-observer-1.1.0.tgz", + "integrity": "sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rc-component/portal/-/portal-1.1.2.tgz", + "integrity": "sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/qrcode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.0.0.tgz", + "integrity": "sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/tour": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@rc-component/tour/-/tour-1.15.1.tgz", + "integrity": "sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/portal": "^1.0.0-9", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.2", + "rc-util": "^5.24.4" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@rc-component/trigger": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.6.tgz", + "integrity": "sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.2", + "@rc-component/portal": "^1.1.0", + "classnames": "^2.3.2", + "rc-motion": "^2.0.0", + "rc-resize-observer": "^1.3.1", + "rc-util": "^5.44.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.0.tgz", + "integrity": "sha512-dlzb07f5LDY+tzs+iLCSXV2yuhaYfezqyZQc+n6baLECWkOMEWxkECAOnXL0ba7lsA25fM9b2jtzpu/uxo1a7g==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@shikijs/core": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.6.0.tgz", + "integrity": "sha512-9By7Xb3olEX0o6UeJyPLI1PE1scC4d3wcVepvtv2xbuN9/IThYN4Wcwh24rcFeASzPam11MCq8yQpwwzCgSBRw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.6.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.6.0.tgz", + "integrity": "sha512-7YnLhZG/TU05IHMG14QaLvTW/9WiK8SEYafceccHUSXs2Qr5vJibUwsDfXDLmRi0zHdzsxrGKpSX6hnqe0k8nA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.6.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.6.0.tgz", + "integrity": "sha512-nmOhIZ9yT3Grd+2plmW/d8+vZ2pcQmo/UnVwXMUXAKTXdi+LK0S08Ancrz5tQQPkxvjBalpMW2aKvwXfelauvA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.6.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.6.0.tgz", + "integrity": "sha512-IdZkQJaLBu1LCYCwkr30hNuSDfllOT8RWYVZK1tD2J03DkiagYKRxj/pDSl8Didml3xxuyzUjgtioInwEQM/TA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.6.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.6.0.tgz", + "integrity": "sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.6.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-3.6.0.tgz", + "integrity": "sha512-PYkU54lYV0RCaUG8n2FNTF+YWiU3uPhcjLGq2x/C8lIrUX9GVnRb3bK+R5xtdFHbuctntATKm7ondp/H/dux9Q==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.6.0", + "@shikijs/types": "3.6.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.6.0.tgz", + "integrity": "sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "license": "BSD-3-Clause" + }, + "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/@splinetool/runtime": { + "version": "0.9.526", + "resolved": "https://registry.npmjs.org/@splinetool/runtime/-/runtime-0.9.526.tgz", + "integrity": "sha512-qznHbXA5aKwDbCgESAothCNm1IeEZcmNWG145p5aXj4w5uoqR1TZ9qkTHTKLTsUbHeitCwdhzmRqan1kxboLgQ==", + "dependencies": { + "on-change": "^4.0.0", + "semver-compare": "^1.0.0" + } + }, + "node_modules/@standard-schema/spec": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", + "integrity": "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@stitches/react": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/react/-/react-1.2.8.tgz", + "integrity": "sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.3.0" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", + "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "tslib": "^2.4.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/@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/@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/conventional-commits-parser": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.1.tgz", + "integrity": "sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/diff-match-patch": { + "version": "1.0.36", + "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz", + "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/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/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "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/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdx": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "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==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.0.tgz", + "integrity": "sha512-yZQa2zm87aRVcqDyH5+4Hv9KYgSdgwX1rFnGvpbzMaC7YAljmhBET93TPiTd3ObwTL+gSpIzPKg5BqVxdCvxKg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" + }, + "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/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.1.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.7.tgz", + "integrity": "sha512-BnsPLV43ddr05N71gaGzyZ5hzkCmGwhMvYc8zmvI8Ci1bRkkDSzDDVfAXfN2tk748OwI7ediiPX6PfT9p0QGVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "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/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", + "license": "MIT" + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "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==", + "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/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/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "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/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", + "engines": { + "node": ">=12.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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/ahooks": { + "version": "3.8.5", + "resolved": "https://registry.npmjs.org/ahooks/-/ahooks-3.8.5.tgz", + "integrity": "sha512-Y+MLoJpBXVdjsnnBjE5rOSPkQ4DK+8i5aPDzLJdIOsCpo/fiAeXcBY1Y7oWgtOK0TpOz0gFa/XcyO1UGdoqLcw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "dayjs": "^1.9.1", + "intersection-observer": "^0.12.0", + "js-cookie": "^3.0.5", + "lodash": "^4.17.21", + "react-fast-compare": "^3.2.2", + "resize-observer-polyfill": "^1.5.1", + "screenfull": "^5.0.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=8.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ai": { + "version": "4.3.16", + "resolved": "https://registry.npmjs.org/ai/-/ai-4.3.16.tgz", + "integrity": "sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.1.3", + "@ai-sdk/provider-utils": "2.2.8", + "@ai-sdk/react": "1.2.12", + "@ai-sdk/ui-utils": "1.2.11", + "@opentelemetry/api": "1.9.0", + "jsondiffpatch": "0.6.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/antd": { + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.26.0.tgz", + "integrity": "sha512-iMPYKFTo2HvIRGutUOuN5AG+Uf+B2QaqcGQbdPp/100fqV3FAil6vFZLVuV3C4XEUOlDNkkUlJKhLR9V5rzIEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ant-design/colors": "^7.2.1", + "@ant-design/cssinjs": "^1.23.0", + "@ant-design/cssinjs-utils": "^1.1.3", + "@ant-design/fast-color": "^2.0.6", + "@ant-design/icons": "^5.6.1", + "@ant-design/react-slick": "~1.1.2", + "@babel/runtime": "^7.26.0", + "@rc-component/color-picker": "~2.0.1", + "@rc-component/mutate-observer": "^1.1.0", + "@rc-component/qrcode": "~1.0.0", + "@rc-component/tour": "~1.15.1", + "@rc-component/trigger": "^2.2.6", + "classnames": "^2.5.1", + "copy-to-clipboard": "^3.3.3", + "dayjs": "^1.11.11", + "rc-cascader": "~3.34.0", + "rc-checkbox": "~3.5.0", + "rc-collapse": "~3.9.0", + "rc-dialog": "~9.6.0", + "rc-drawer": "~7.3.0", + "rc-dropdown": "~4.2.1", + "rc-field-form": "~2.7.0", + "rc-image": "~7.12.0", + "rc-input": "~1.8.0", + "rc-input-number": "~9.5.0", + "rc-mentions": "~2.20.0", + "rc-menu": "~9.16.1", + "rc-motion": "^2.9.5", + "rc-notification": "~5.6.4", + "rc-pagination": "~5.1.0", + "rc-picker": "~4.11.3", + "rc-progress": "~4.0.0", + "rc-rate": "~2.13.1", + "rc-resize-observer": "^1.4.3", + "rc-segmented": "~2.7.0", + "rc-select": "~14.16.8", + "rc-slider": "~11.1.8", + "rc-steps": "~6.0.1", + "rc-switch": "~4.1.0", + "rc-table": "~7.51.0", + "rc-tabs": "~15.6.1", + "rc-textarea": "~1.10.0", + "rc-tooltip": "~6.4.0", + "rc-tree": "~5.13.1", + "rc-tree-select": "~5.27.0", + "rc-upload": "~4.9.2", + "rc-util": "^5.44.4", + "scroll-into-view-if-needed": "^3.1.0", + "throttle-debounce": "^5.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ant-design" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/antd-style": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/antd-style/-/antd-style-3.7.1.tgz", + "integrity": "sha512-CQOfddVp4aOvBfCepa+Kj2e7ap+2XBINg1Kn2osdE3oQvrD7KJu/K0sfnLcFLkgCJygbxmuazYdWLKb+drPDYA==", + "license": "MIT", + "dependencies": { + "@ant-design/cssinjs": "^1.21.1", + "@babel/runtime": "^7.24.1", + "@emotion/cache": "^11.11.0", + "@emotion/css": "^11.11.2", + "@emotion/react": "^11.11.4", + "@emotion/serialize": "^1.1.3", + "@emotion/utils": "^1.2.1", + "use-merge-value": "^1.2.0" + }, + "peerDependencies": { + "antd": ">=5.8.1", + "react": ">=18" + } + }, + "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/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/app-builder-lib/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/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/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "license": "ISC", + "optional": true + }, + "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/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/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", + "dev": true, + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "license": "MIT" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.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==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/astring": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", + "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", + "license": "MIT", + "bin": { + "astring": "bin/astring" + } + }, + "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/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==", + "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/attr-accept": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", + "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/aws-sign2": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", + "integrity": "sha512-JnJpAS0p9RmixkOvW2XwDxxzs1bd4/VAGIl6Q0EC5YOo+p+hqIhtDhn/nmFnB/xUNXbLkpE2mOjgVIBRKD4xYw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/babel-plugin-macros/node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/babel-plugin-macros/node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", + "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.4", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", + "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.3", + "core-js-compat": "^3.40.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", + "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.4" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true, + "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/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "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", + "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/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/boom": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", + "integrity": "sha512-KbiZEa9/vofNcVJXGwdWWn25reQ3V3dHBWbS07FTF3/TOehLnm9GEhJV4T6ZvGPkShRpmUqYwnaCrkj0mRnP6Q==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "license": "BSD-3-Clause", + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "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-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==", + "dev": true, + "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/builder-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/builder-util/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/builder-util/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/builder-util/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/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/builder-util/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/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "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-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/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001722", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001722.tgz", + "integrity": "sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA==", + "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/canvas": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", + "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "nan": "^2.17.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/canvas/node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/canvas/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/canvas/node_modules/simple-get": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", + "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/caseless": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", + "integrity": "sha512-ODLXH644w9C2fMPAm7bMDQ3GRvipZWZfKc+8As6hIadRIelE0n0xZuN38NS6kiK3KPEVrpymmQD8bvncAHWQkQ==", + "license": "Apache-2.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "devOptional": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/chroma-js": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-3.1.2.tgz", + "integrity": "sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "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/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "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/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/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/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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-prompt": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/co-prompt/-/co-prompt-1.0.0.tgz", + "integrity": "sha512-uKmEbjDnL9SJTb+TNfIFsATe1F3IsNsR7KDGUG1hq7ColkMV0MSn7dg3eKVS+3wwtyvVqrgfIwi39NOJiknO7Q==", + "license": "MIT", + "dependencies": { + "keypress": "~0.2.1" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/collapse-white-space": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", + "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "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/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "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/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/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/compare-func": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-ify": "^1.0.0", + "dot-prop": "^5.1.0" + } + }, + "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/compute-scroll-into-view": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", + "integrity": "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==", + "license": "MIT", + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-8.2.2.tgz", + "integrity": "sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "date-fns": "^2.30.0", + "lodash": "^4.17.21", + "rxjs": "^7.8.1", + "shell-quote": "^1.8.1", + "spawn-command": "0.0.2", + "supports-color": "^8.1.1", + "tree-kill": "^1.2.2", + "yargs": "^17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": "^14.13.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/concurrently/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/concurrently/node_modules/chalk/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/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, + "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/config-file-ts/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "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/config-file-ts/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/config-file-ts/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/console-table-printer": { + "version": "2.14.3", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.14.3.tgz", + "integrity": "sha512-X5OCFnjYlXzRuC8ac5hPA2QflRjJvNKJocMhlnqK/Ap7q3DHXr0NJ0TGzwmEKOiOdJrjsSwEd0m+a32JAYPrKQ==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, + "node_modules/conventional-changelog-angular": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", + "integrity": "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-changelog-conventionalcommits": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", + "integrity": "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==", + "dev": true, + "license": "ISC", + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/conventional-commits-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", + "integrity": "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "conventional-commits-parser": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, + "node_modules/copy-to-clipboard": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", + "integrity": "sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==", + "license": "MIT", + "peer": true, + "dependencies": { + "toggle-selection": "^1.0.6" + } + }, + "node_modules/core-js-compat": { + "version": "3.43.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.43.0.tgz", + "integrity": "sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA==", + "license": "MIT", + "dependencies": { + "browserslist": "^4.25.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig-typescript-loader": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.1.0.tgz", + "integrity": "sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "jiti": "^2.4.1" + }, + "engines": { + "node": ">=v18" + }, + "peerDependencies": { + "@types/node": "*", + "cosmiconfig": ">=9", + "typescript": ">=5" + } + }, + "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==", + "license": "Apache-2.0", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cryptiles": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", + "integrity": "sha512-FFN5KwpvvQTTS5hWPxrU8/QE4kQUc6uwZcrnlMBN82t1MgAtq8mnoDwINBly9Tdr02seeIIhtdF+UH1feBYGog==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "license": "BSD-3-Clause", + "dependencies": { + "boom": "2.x.x" + }, + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT" + }, + "node_modules/cytoscape": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.32.0.tgz", + "integrity": "sha512-5JHBC9n75kz5851jeklCPmZWcg3hUe6sjqJvyk3+hVqFaKcHwHgxsjeN1yLmggoUc6STbtm9/NQyabQehfjvWQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "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-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "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-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/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/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-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "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-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "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-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "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/dagre-d3-es": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.11.tgz", + "integrity": "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dargs": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", + "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", + "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decode-uri-component": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz", + "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==", + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "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/decompress-response/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/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-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/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, + "node_modules/dingbat-to-unicode": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dingbat-to-unicode/-/dingbat-to-unicode-1.0.1.tgz", + "integrity": "sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==", + "license": "BSD-2-Clause" + }, + "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/dir-compare/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/dir-compare/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/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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-builder/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/dmg-builder/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/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/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/dmg-license/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", + "optional": true, + "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/dmg-license/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", + "optional": true + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dompurify": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.6.tgz", + "integrity": "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/duck": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/duck/-/duck-0.1.12.tgz", + "integrity": "sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==", + "license": "BSD", + "dependencies": { + "underscore": "^1.13.1" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "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": "35.5.1", + "resolved": "https://registry.npmjs.org/electron/-/electron-35.5.1.tgz", + "integrity": "sha512-kkbGXz56safvXcxqAZyMS2nJGYK9NFG/iKOJsAO5e0HPH8y3EnV4Fi87tvbfFpeLiaruFS+eN0YFy6f1StpSgQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^22.7.7", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-build": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/electron-build/-/electron-build-0.0.3.tgz", + "integrity": "sha512-gJ+Civsa9MfR4TssR1+XiJczYylYGoSD2mFMHcOimRV0JxeHA2bbqYJ1Usb9b61F+QuG6dN+l+wLGh7680zH+A==", + "license": "MIT", + "dependencies": { + "co": "4.6.0", + "co-prompt": "1.0.0", + "commander": "2.9.0", + "jszip": "2.5.0", + "path": "0.12.7", + "progress": "1.1.8", + "q": "1.4.1", + "request": "2.72.0", + "url": "0.11.0" + }, + "bin": { + "ebuild": "index.js" + } + }, + "node_modules/electron-build/node_modules/commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", + "license": "MIT", + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/electron-build/node_modules/jszip": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-2.5.0.tgz", + "integrity": "sha512-IRoyf8JSYY3nx+uyh5xPc0qdy8pUDTp2UkHOWYNF/IO/3D8nx7899UlSAjD8rf8wUgOmm0lACWx/GbW3EaxIXQ==", + "license": "MIT or GPLv3", + "dependencies": { + "pako": "~0.2.5" + } + }, + "node_modules/electron-build/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "node_modules/electron-build/node_modules/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", + "engines": { + "node": ">=0.4.0" + } + }, + "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-builder-squirrel-windows/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", + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder-squirrel-windows/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder-squirrel-windows/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", + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-builder/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/electron-builder/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/electron-builder/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/electron-builder/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/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/electron-builder/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/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-publish/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/electron-publish/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/electron-publish/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/electron-publish/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/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/electron-publish/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/electron-to-chromium": { + "version": "1.5.166", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.166.tgz", + "integrity": "sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw==", + "license": "ISC" + }, + "node_modules/electron-updater": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.6.2.tgz", + "integrity": "sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==", + "license": "MIT", + "dependencies": { + "builder-util-runtime": "9.3.1", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "^7.6.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/builder-util-runtime": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.3.1.tgz", + "integrity": "sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/electron-updater/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==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron/node_modules/@types/node": { + "version": "22.15.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz", + "integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-mart": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", + "integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/enumify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/enumify/-/enumify-1.0.4.tgz", + "integrity": "sha512-5mwWXaVzJaqyUdOW/PDH5QySRgmQ8VvujmxmvXoXj9w0n+6omhVuyD56eI37FMqy/LxueJzsQ4DrHVQzuT/TXg==", + "license": "MIT" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "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==", + "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/es-toolkit": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.43.0.tgz", + "integrity": "sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "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/esast-util-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", + "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/esast-util-from-js": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", + "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "acorn": "^8.0.0", + "esast-util-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-attach-comments": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", + "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-build-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", + "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "estree-walker": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", + "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", + "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "astring": "^1.8.0", + "source-map": "^0.7.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-util-to-js/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 8" + } + }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/exsolve": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.5.tgz", + "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", + "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/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend-shallow/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", + "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/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/file-selector": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.5.0.tgz", + "integrity": "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + }, + "engines": { + "node": ">= 10" + } + }, + "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/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz", + "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", + "integrity": "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "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/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "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/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/framer-motion": { + "version": "12.17.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.17.0.tgz", + "integrity": "sha512-2hISKgDk49yCLStwG1wf4Kdy/D6eBw9/eRNaWFIYoI9vMQ/Mqd1Fz+gzVlEtxJmtQ9y4IWnXm19/+UXD3dAYAA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.17.0", + "motion-utils": "^12.12.1", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "node_modules/from2/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", + "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/from2/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" + }, + "node_modules/from2/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", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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" + }, + "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/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.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==", + "devOptional": true, + "license": "ISC" + }, + "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/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gauge/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", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/gauge/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", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/giscus": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/giscus/-/giscus-1.6.0.tgz", + "integrity": "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==", + "license": "MIT", + "dependencies": { + "lit": "^3.2.1" + } + }, + "node_modules/git-raw-commits": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", + "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dargs": "^8.0.0", + "meow": "^12.0.1", + "split2": "^4.0.0" + }, + "bin": { + "git-raw-commits": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/github-markdown-css": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/github-markdown-css/-/github-markdown-css-5.8.1.tgz", + "integrity": "sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "devOptional": 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/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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-directory": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", + "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "4.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "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==", + "license": "ISC" + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", + "license": "MIT" + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, + "node_modules/har-validator": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", + "integrity": "sha512-P6tFV+wCcUL3nbyTDAvveDySfbhy0XkDtAIfZP6HITjM2WUsiPna/Eg1Yy93SFXvahqoX+kt0n+6xlXKDXYowA==", + "deprecated": "this library is no longer supported", + "license": "ISC", + "dependencies": { + "chalk": "^1.1.1", + "commander": "^2.9.0", + "is-my-json-valid": "^2.12.4", + "pinkie-promise": "^2.0.0" + }, + "bin": { + "har-validator": "bin/har-validator" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/har-validator/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-validator/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-validator/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-validator/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/har-validator/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/har-validator/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-validator/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "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/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "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/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-estree": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", + "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-attach-comments": "^3.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hawk": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", + "integrity": "sha512-X8xbmTc1cbPXcQV4WkLcRMALuyoxhfpFATmyuCxJPOAvrDS4DNnsTAOmKUxMTOWU6TzrTOkxPKwIx5ZOpJVSrg==", + "deprecated": "This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "license": "BSD-3-Clause", + "dependencies": { + "boom": "2.x.x", + "cryptiles": "2.x.x", + "hoek": "2.x.x", + "sntp": "1.x.x" + }, + "engines": { + "node": ">=0.10.32" + } + }, + "node_modules/hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha512-V6Yw1rIcYV/4JsnggjBU0l4Kr+EXhpwqXRusENU1Xx6ro00IHPHYNynCuBTOZAPlr3AAmLvchH9I7N/VUdvOwQ==", + "deprecated": "This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial).", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.40" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "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/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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-signature": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", + "integrity": "sha512-iUn0NcRULlDGtqNLN1Jxmzayk8ogm7NToldASyZBpM2qggbphjXzNOiw3piN8tgz+e/DRs6X5gAzFwTI6BCRcg==", + "license": "MIT", + "dependencies": { + "assert-plus": "^0.2.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http-signature/node_modules/assert-plus": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", + "integrity": "sha512-u1L0ZLywRziOVjUhRxI0Qg9G+4RnFB9H/Rq40YWn0dieDgO7vAYeJz6jKAO6t/aruzlDFLAPkQTT87e+f8Imaw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, + "node_modules/i18next": { + "version": "24.2.3", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz", + "integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.10" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.2.0.tgz", + "integrity": "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "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==", + "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/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/image-size": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", + "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", + "license": "MIT", + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "devOptional": 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/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "license": "Apache-2.0" + }, + "node_modules/into-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-6.0.0.tgz", + "integrity": "sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "from2": "^2.3.0", + "p-is-promise": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" + }, + "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.9.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", + "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-my-ip-valid": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz", + "integrity": "sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg==", + "license": "MIT" + }, + "node_modules/is-my-json-valid": { + "version": "2.20.6", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz", + "integrity": "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==", + "license": "MIT", + "dependencies": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^5.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-text-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-2.0.0.tgz", + "integrity": "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz", + "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==", + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "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==", + "dev": true, + "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.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jake/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jake/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/jake/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/jake/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/jake/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/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/jotai": { + "version": "2.12.5", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.12.5.tgz", + "integrity": "sha512-G8m32HW3lSmcz/4mbqx0hgJIQ0ekndKWiYP7kWVKi0p6saLXdSoye+FZiOFyonnd7Q482LCzm8sMDl7Ar1NWDw==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/js-tiktoken": { + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/js-tiktoken/-/js-tiktoken-1.0.20.tgz", + "integrity": "sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.5.1" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "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==", + "license": "ISC" + }, + "node_modules/json2mq": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", + "integrity": "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==", + "license": "MIT", + "peer": true, + "dependencies": { + "string-convert": "^0.2.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsondiffpatch": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz", + "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==", + "license": "MIT", + "dependencies": { + "@types/diff-match-patch": "^1.0.36", + "chalk": "^5.3.0", + "diff-match-patch": "^1.0.5" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "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/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ], + "license": "MIT" + }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jsonrepair": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.13.1.tgz", + "integrity": "sha512-WJeiE0jGfxYmtLwBTEk8+y/mYcaleyLXWaqp5bJu0/ZTSeG0KQq/wWQ8pmnkKenEdN6pdnn6QtcoSUkbqDHWNw==", + "license": "ISC", + "bin": { + "jsonrepair": "bin/cli.js" + } + }, + "node_modules/JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/jsprim/node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/jsprim/node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/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==", + "license": "MIT", + "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/jszip/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==", + "license": "MIT" + }, + "node_modules/jszip/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==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/katex": { + "version": "0.16.22", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.22.tgz", + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/keypress": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz", + "integrity": "sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==", + "license": "MIT" + }, + "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/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/langchain": { + "version": "0.3.27", + "resolved": "https://registry.npmjs.org/langchain/-/langchain-0.3.27.tgz", + "integrity": "sha512-XfOuXetMSpkS11Mt6YJkDmvuSGTMPUsks5DJz4RCZ3y2dcbLkOe5kecjx2SWVJYqQIqcMMwsjsve3/ZjnRe7rQ==", + "license": "MIT", + "dependencies": { + "@langchain/openai": ">=0.1.0 <0.6.0", + "@langchain/textsplitters": ">=0.0.0 <0.2.0", + "js-tiktoken": "^1.0.12", + "js-yaml": "^4.1.0", + "jsonpointer": "^5.0.1", + "langsmith": "^0.3.29", + "openapi-types": "^12.1.3", + "p-retry": "4", + "uuid": "^10.0.0", + "yaml": "^2.2.1", + "zod": "^3.22.4", + "zod-to-json-schema": "^3.22.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@langchain/anthropic": "*", + "@langchain/aws": "*", + "@langchain/cerebras": "*", + "@langchain/cohere": "*", + "@langchain/core": ">=0.2.21 <0.4.0", + "@langchain/deepseek": "*", + "@langchain/google-genai": "*", + "@langchain/google-vertexai": "*", + "@langchain/google-vertexai-web": "*", + "@langchain/groq": "*", + "@langchain/mistralai": "*", + "@langchain/ollama": "*", + "@langchain/xai": "*", + "axios": "*", + "cheerio": "*", + "handlebars": "^4.7.8", + "peggy": "^3.0.2", + "typeorm": "*" + }, + "peerDependenciesMeta": { + "@langchain/anthropic": { + "optional": true + }, + "@langchain/aws": { + "optional": true + }, + "@langchain/cerebras": { + "optional": true + }, + "@langchain/cohere": { + "optional": true + }, + "@langchain/deepseek": { + "optional": true + }, + "@langchain/google-genai": { + "optional": true + }, + "@langchain/google-vertexai": { + "optional": true + }, + "@langchain/google-vertexai-web": { + "optional": true + }, + "@langchain/groq": { + "optional": true + }, + "@langchain/mistralai": { + "optional": true + }, + "@langchain/ollama": { + "optional": true + }, + "@langchain/xai": { + "optional": true + }, + "axios": { + "optional": true + }, + "cheerio": { + "optional": true + }, + "handlebars": { + "optional": true + }, + "peggy": { + "optional": true + }, + "typeorm": { + "optional": true + } + } + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/langsmith": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/langsmith/-/langsmith-0.3.31.tgz", + "integrity": "sha512-9lwuLZuN3tXFYQ6eMg0rmbBw7oxQo4bu1NYeylbjz27bOdG1XB9XNoxaiIArkK4ciLdOIOhPMBXP4bkvZOgHRw==", + "license": "MIT", + "dependencies": { + "@types/uuid": "^10.0.0", + "chalk": "^4.1.2", + "console-table-printer": "^2.12.1", + "p-queue": "^6.6.2", + "p-retry": "4", + "semver": "^7.6.3", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "openai": "*" + }, + "peerDependenciesMeta": { + "openai": { + "optional": true + } + } + }, + "node_modules/langsmith/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/langsmith/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/langsmith/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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "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==", + "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/leva": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/leva/-/leva-0.10.0.tgz", + "integrity": "sha512-RiNJWmeqQdKIeHuVXgshmxIHu144a2AMYtLxKf8Nm1j93pisDPexuQDHKNdQlbo37wdyDQibLjY9JKGIiD7gaw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-portal": "1.0.2", + "@radix-ui/react-tooltip": "1.0.5", + "@stitches/react": "^1.2.8", + "@use-gesture/react": "^10.2.5", + "colord": "^2.9.2", + "dequal": "^2.0.2", + "merge-value": "^1.0.0", + "react-colorful": "^5.5.1", + "react-dropzone": "^12.0.0", + "v8n": "^1.3.3", + "zustand": "^3.6.9" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "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==", + "dev": true, + "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/lint-staged": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz", + "integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/commander": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/listr2": { + "version": "8.3.3", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", + "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/listr2/node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/listr2/node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/listr2/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lit": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", + "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.0.tgz", + "integrity": "sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.0.tgz", + "integrity": "sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/local-pkg": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.1.tgz", + "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.0.1", + "quansync": "^0.2.8" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "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.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "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==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "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" + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.startcase": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.upperfirst": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", + "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lop": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/lop/-/lop-0.4.2.tgz", + "integrity": "sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==", + "license": "BSD-2-Clause", + "dependencies": { + "duck": "^0.1.12", + "option": "~0.2.1", + "underscore": "^1.13.1" + } + }, + "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": "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/lucide-react": { + "version": "0.469.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz", + "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/mammoth": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/mammoth/-/mammoth-1.9.1.tgz", + "integrity": "sha512-4S2v1eP4Yo4so0zGNicJKcP93su3wDPcUk+xvkjSG75nlNjSkDJu8BhWQ+e54BROM0HfA6nPzJn12S6bq2Ko6w==", + "license": "BSD-2-Clause", + "dependencies": { + "@xmldom/xmldom": "^0.8.6", + "argparse": "~1.0.3", + "base64-js": "^1.5.1", + "bluebird": "~3.4.0", + "dingbat-to-unicode": "^1.0.1", + "jszip": "^3.7.1", + "lop": "^0.4.2", + "path-is-absolute": "^1.0.0", + "underscore": "^1.13.1", + "xmlbuilder": "^10.0.0" + }, + "bin": { + "mammoth": "bin/mammoth" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/mammoth/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/mammoth/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "license": "MIT" + }, + "node_modules/mammoth/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/mammoth/node_modules/xmlbuilder": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-10.1.1.tgz", + "integrity": "sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/markdown-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", + "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.12.tgz", + "integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "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/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-newline-to-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", + "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/meow": { + "version": "12.1.1", + "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", + "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/merge-value/-/merge-value-1.0.0.tgz", + "integrity": "sha512-fJMmvat4NeKz63Uv9iHWcPDjCWcCkoiRoajRTEO8hlhUC6rwaHg0QCF9hBOTjZmm4JuglPckPSTtcuJL5kp0TQ==", + "license": "MIT", + "dependencies": { + "get-value": "^2.0.6", + "is-extendable": "^1.0.0", + "mixin-deep": "^1.2.0", + "set-value": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/mermaid": { + "version": "11.6.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.6.0.tgz", + "integrity": "sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.0.4", + "@iconify/utils": "^2.1.33", + "@mermaid-js/parser": "^0.4.0", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.11", + "dayjs": "^1.11.13", + "dompurify": "^3.2.4", + "katex": "^0.16.9", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^15.0.7", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/mermaid/node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "node_modules/mermaid/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/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==", + "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==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "devOptional": true, + "license": "ISC", + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "devOptional": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "license": "MIT", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "devOptional": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.4.tgz", + "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "pathe": "^2.0.1", + "pkg-types": "^1.3.0", + "ufo": "^1.5.4" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/motion-dom": { + "version": "12.17.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.17.0.tgz", + "integrity": "sha512-FA6/c70R9NKs3g41XDVONzmUUrEmyaifLVGCWtAmHP0usDnX9W+RN/tmbC4EUl0w6yLGvMTOwnWCFVgA5luhRg==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.12.1" + } + }, + "node_modules/motion-utils": { + "version": "12.12.1", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.12.1.tgz", + "integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==", + "license": "MIT" + }, + "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/multistream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/multistream/-/multistream-4.1.0.tgz", + "integrity": "sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw==", + "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": { + "once": "^1.4.0", + "readable-stream": "^3.6.0" + } + }, + "node_modules/mustache": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", + "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", + "license": "MIT", + "peer": true, + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/nan": { + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "14.2.29", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.29.tgz", + "integrity": "sha512-s98mCOMOWLGGpGOfgKSnleXLuegvvH415qtRZXpSp00HeEgdmrxmwL9cgKU+h4XrhB16zEI5d/7BnkS3ATInsA==", + "license": "MIT", + "dependencies": { + "@next/env": "14.2.29", + "@swc/helpers": "0.5.5", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "graceful-fs": "^4.2.11", + "postcss": "8.4.31", + "styled-jsx": "5.1.1" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=18.17.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "14.2.29", + "@next/swc-darwin-x64": "14.2.29", + "@next/swc-linux-arm64-gnu": "14.2.29", + "@next/swc-linux-arm64-musl": "14.2.29", + "@next/swc-linux-x64-gnu": "14.2.29", + "@next/swc-linux-x64-musl": "14.2.29", + "@next/swc-win32-arm64-msvc": "14.2.29", + "@next/swc-win32-ia32-msvc": "14.2.29", + "@next/swc-win32-x64-msvc": "14.2.29" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.2.1.tgz", + "integrity": "sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==", + "license": "MIT", + "peerDependencies": { + "next": "*", + "react": "*", + "react-dom": "*" + } + }, + "node_modules/node-abi": { + "version": "3.75.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", + "integrity": "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "deprecated": "Use your platform's native DOMException instead", + "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": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, + "node_modules/node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA==", + "deprecated": "Use uuid module instead", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, + "node_modules/numeral": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/numeral/-/numeral-2.0.6.tgz", + "integrity": "sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha512-VlF07iu3VV3+BTXj43Nmp6Irt/G7j/NgEctUS6IweH1RGhURjjCc2NWtzXFPXXWWfc7hgbXQdtiQu2LGp6MxUg==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "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-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/ollama-ai-provider": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ollama-ai-provider/-/ollama-ai-provider-1.2.0.tgz", + "integrity": "sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "^1.0.0", + "@ai-sdk/provider-utils": "^2.0.0", + "partial-json": "0.1.7" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/on-change": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/on-change/-/on-change-4.0.2.tgz", + "integrity": "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/on-change?sponsor=1" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/openai/node_modules/@types/node": { + "version": "18.19.111", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.111.tgz", + "integrity": "sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/openai/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==", + "license": "MIT" + }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT" + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "license": "(WTFPL OR MIT)", + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/option": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/option/-/option-0.2.4.tgz", + "integrity": "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==", + "license": "BSD-2-Clause" + }, + "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-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-3.0.0.tgz", + "integrity": "sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", + "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-queue/node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/p-retry": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", + "license": "MIT", + "dependencies": { + "@types/retry": "0.12.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/package-manager-detector": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.3.0.tgz", + "integrity": "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==", + "license": "MIT" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/partial-json": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", + "license": "MIT" + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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==", + "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==", + "dev": true, + "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==", + "dev": true, + "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/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/pdf2md-js": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/pdf2md-js/-/pdf2md-js-1.0.8.tgz", + "integrity": "sha512-cjKv46RzWmUNCt0mOgr/HGOJJl1fJmNOaldx+tdwlxTiZojZVBkGwI27HLPbms5zH+XRdJiCwjynnZZuKQygvQ==", + "license": "MIT", + "dependencies": { + "@hyzyla/pdfium": "^2.1.7", + "fs-extra": "^11.3.0", + "sharp": "^0.33.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/pdf2md-js/node_modules/fs-extra": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", + "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/pdf2md-js/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/pdf2md-js/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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/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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "license": "MIT", + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pkg": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/pkg/-/pkg-5.8.1.tgz", + "integrity": "sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "7.18.2", + "@babel/parser": "7.18.4", + "@babel/types": "7.19.0", + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "globby": "^11.1.0", + "into-stream": "^6.0.0", + "is-core-module": "2.9.0", + "minimist": "^1.2.6", + "multistream": "^4.1.0", + "pkg-fetch": "3.4.2", + "prebuild-install": "7.1.1", + "resolve": "^1.22.0", + "stream-meter": "^1.0.4" + }, + "bin": { + "pkg": "lib-es5/bin.js" + }, + "peerDependencies": { + "node-notifier": ">=9.0.1" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/pkg-fetch": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.4.2.tgz", + "integrity": "sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "fs-extra": "^9.1.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.6", + "progress": "^2.0.3", + "semver": "^7.3.5", + "tar-fs": "^2.1.1", + "yargs": "^16.2.0" + }, + "bin": { + "pkg-fetch": "lib-es5/bin.js" + } + }, + "node_modules/pkg-fetch/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pkg-fetch/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/pkg-fetch/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/pkg-fetch/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/pkg-fetch/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/pkg-fetch/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-fetch/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/pkg-fetch/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/pkg-fetch/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/pkg-fetch/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg-fetch/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/pkg-types": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", + "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.1", + "exsolve": "^1.0.1", + "pathe": "^2.0.3" + } + }, + "node_modules/pkg/node_modules/@babel/generator": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.2.tgz", + "integrity": "sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.18.2", + "@jridgewell/gen-mapping": "^0.3.0", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/pkg/node_modules/@babel/parser": { + "version": "7.18.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.4.tgz", + "integrity": "sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/pkg/node_modules/@babel/types": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", + "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.18.10", + "@babel/helper-validator-identifier": "^7.18.6", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/pkg/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pkg/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/pkg/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/pkg/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pkg/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/pkg/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/pkg/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/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/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/polished": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz", + "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/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/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prisma": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.9.0.tgz", + "integrity": "sha512-resJAwMyZREC/I40LF6FZ6rZTnlrlrYrb63oW37Gq+U+9xHwbyMSPJjKtM7VZf3gTO86t/Oyz+YeSXr3CmAY1Q==", + "devOptional": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@prisma/config": "6.9.0", + "@prisma/engines": "6.9.0" + }, + "bin": { + "prisma": "build/index.js" + }, + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "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", + "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==", + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "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/promise-retry/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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", + "dev": true, + "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/q": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "integrity": "sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" + } + }, + "node_modules/qs": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.1.2.tgz", + "integrity": "sha512-vkyEo9cSlcgr1xj5n14ykoPWKE36R8wkxK2fQkbGACZNv4zDGFw/juEwFFUs9/APU7DaTMRlRNTYISLPD0Z4Qw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/quansync": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.10.tgz", + "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/query-string": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.2.0.tgz", + "integrity": "sha512-YIRhrHujoQxhexwRLxfy3VSjOXmvZRd2nyw1PwL1UUqZ/ys1dEZd1+NSgXkne2l/4X/7OXkigEAuhTX0g/ivJQ==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.4.1", + "filter-obj": "^5.1.0", + "split-on-first": "^3.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/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/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc-cascader": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", + "integrity": "sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "^2.3.1", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-checkbox": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rc-checkbox/-/rc-checkbox-3.5.0.tgz", + "integrity": "sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.25.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-collapse": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/rc-collapse/-/rc-collapse-3.9.0.tgz", + "integrity": "sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.3.4", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dialog": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/rc-dialog/-/rc-dialog-9.6.0.tgz", + "integrity": "sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/portal": "^1.0.0-8", + "classnames": "^2.2.6", + "rc-motion": "^2.3.0", + "rc-util": "^5.21.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-drawer": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/rc-drawer/-/rc-drawer-7.3.0.tgz", + "integrity": "sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.23.9", + "@rc-component/portal": "^1.1.1", + "classnames": "^2.2.6", + "rc-motion": "^2.6.1", + "rc-util": "^5.38.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-dropdown": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/rc-dropdown/-/rc-dropdown-4.2.1.tgz", + "integrity": "sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-util": "^5.44.1" + }, + "peerDependencies": { + "react": ">=16.11.0", + "react-dom": ">=16.11.0" + } + }, + "node_modules/rc-field-form": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.7.0.tgz", + "integrity": "sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.0", + "@rc-component/async-validator": "^5.0.3", + "rc-util": "^5.32.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-footer": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/rc-footer/-/rc-footer-0.6.8.tgz", + "integrity": "sha512-JBZ+xcb6kkex8XnBd4VHw1ZxjV6kmcwUumSHaIFdka2qzMCo7Klcy4sI6G0XtUpG/vtpislQCc+S9Bc+NLHYMg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-image": { + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/rc-image/-/rc-image-7.12.0.tgz", + "integrity": "sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/portal": "^1.0.2", + "classnames": "^2.2.6", + "rc-dialog": "~9.6.0", + "rc-motion": "^2.6.2", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.8.0.tgz", + "integrity": "sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.18.1" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-input-number": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.5.0.tgz", + "integrity": "sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/mini-decimal": "^1.0.1", + "classnames": "^2.2.5", + "rc-input": "~1.8.0", + "rc-util": "^5.40.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-mentions": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.20.0.tgz", + "integrity": "sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.22.5", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.6", + "rc-input": "~1.8.0", + "rc-menu": "~9.16.0", + "rc-textarea": "~1.10.0", + "rc-util": "^5.34.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-menu": { + "version": "9.16.1", + "resolved": "https://registry.npmjs.org/rc-menu/-/rc-menu-9.16.1.tgz", + "integrity": "sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.0.0", + "classnames": "2.x", + "rc-motion": "^2.4.3", + "rc-overflow": "^1.3.1", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-motion": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/rc-motion/-/rc-motion-2.9.5.tgz", + "integrity": "sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-util": "^5.44.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-notification": { + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/rc-notification/-/rc-notification-5.6.4.tgz", + "integrity": "sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.9.0", + "rc-util": "^5.20.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-overflow": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rc-overflow/-/rc-overflow-1.4.1.tgz", + "integrity": "sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.37.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-pagination": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/rc-pagination/-/rc-pagination-5.1.0.tgz", + "integrity": "sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.3.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-picker": { + "version": "4.11.3", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.11.3.tgz", + "integrity": "sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.24.7", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.2.1", + "rc-overflow": "^1.3.2", + "rc-resize-observer": "^1.4.0", + "rc-util": "^5.43.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "date-fns": ">= 2.x", + "dayjs": ">= 1.x", + "luxon": ">= 3.x", + "moment": ">= 2.x", + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "peerDependenciesMeta": { + "date-fns": { + "optional": true + }, + "dayjs": { + "optional": true + }, + "luxon": { + "optional": true + }, + "moment": { + "optional": true + } + } + }, + "node_modules/rc-progress": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rc-progress/-/rc-progress-4.0.0.tgz", + "integrity": "sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.6", + "rc-util": "^5.16.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-rate": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/rc-rate/-/rc-rate-2.13.1.tgz", + "integrity": "sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.0.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-resize-observer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/rc-resize-observer/-/rc-resize-observer-1.4.3.tgz", + "integrity": "sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.20.7", + "classnames": "^2.2.1", + "rc-util": "^5.44.1", + "resize-observer-polyfill": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-segmented": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz", + "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.1", + "classnames": "^2.2.1", + "rc-motion": "^2.4.4", + "rc-util": "^5.17.0" + }, + "peerDependencies": { + "react": ">=16.0.0", + "react-dom": ">=16.0.0" + } + }, + "node_modules/rc-select": { + "version": "14.16.8", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.16.8.tgz", + "integrity": "sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/trigger": "^2.1.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-overflow": "^1.3.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-slider": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.8.tgz", + "integrity": "sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.5", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-steps": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/rc-steps/-/rc-steps-6.0.1.tgz", + "integrity": "sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.16.7", + "classnames": "^2.2.3", + "rc-util": "^5.16.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-switch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/rc-switch/-/rc-switch-4.1.0.tgz", + "integrity": "sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0", + "classnames": "^2.2.1", + "rc-util": "^5.30.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-table": { + "version": "7.51.0", + "resolved": "https://registry.npmjs.org/rc-table/-/rc-table-7.51.0.tgz", + "integrity": "sha512-7ZlvW6lB0IDKaSFInD6OfJsCepSJJtfsQv2PZLtzEeZd/PLzQnKliXPaoZqkqDdLdJ3jxE2x4sane4DjxcAg+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "@rc-component/context": "^1.4.0", + "classnames": "^2.2.5", + "rc-resize-observer": "^1.1.0", + "rc-util": "^5.44.3", + "rc-virtual-list": "^3.14.2" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tabs": { + "version": "15.6.1", + "resolved": "https://registry.npmjs.org/rc-tabs/-/rc-tabs-15.6.1.tgz", + "integrity": "sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "classnames": "2.x", + "rc-dropdown": "~4.2.0", + "rc-menu": "~9.16.0", + "rc-motion": "^2.6.2", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.34.1" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-textarea": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.10.0.tgz", + "integrity": "sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "^2.2.1", + "rc-input": "~1.8.0", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.27.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tooltip": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/rc-tooltip/-/rc-tooltip-6.4.0.tgz", + "integrity": "sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.11.2", + "@rc-component/trigger": "^2.0.0", + "classnames": "^2.3.1", + "rc-util": "^5.44.3" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-tree": { + "version": "5.13.1", + "resolved": "https://registry.npmjs.org/rc-tree/-/rc-tree-5.13.1.tgz", + "integrity": "sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.10.1", + "classnames": "2.x", + "rc-motion": "^2.0.1", + "rc-util": "^5.16.1", + "rc-virtual-list": "^3.5.1" + }, + "engines": { + "node": ">=10.x" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-tree-select": { + "version": "5.27.0", + "resolved": "https://registry.npmjs.org/rc-tree-select/-/rc-tree-select-5.27.0.tgz", + "integrity": "sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.7", + "classnames": "2.x", + "rc-select": "~14.16.2", + "rc-tree": "~5.13.0", + "rc-util": "^5.43.0" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "node_modules/rc-upload": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.9.2.tgz", + "integrity": "sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.18.3", + "classnames": "^2.2.5", + "rc-util": "^5.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util": { + "version": "5.44.4", + "resolved": "https://registry.npmjs.org/rc-util/-/rc-util-5.44.4.tgz", + "integrity": "sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.3", + "react-is": "^18.2.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc-util/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/rc-virtual-list": { + "version": "3.18.6", + "resolved": "https://registry.npmjs.org/rc-virtual-list/-/rc-virtual-list-3.18.6.tgz", + "integrity": "sha512-TQ5SsutL3McvWmmxqQtMIbfeoE3dGjJrRSfKekgby7WQMpPIFvv4ghytp5Z0s3D8Nik9i9YNOCqHBfk86AwgAA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.20.0", + "classnames": "^2.2.6", + "rc-resize-observer": "^1.0.0", + "rc-util": "^5.36.0" + }, + "engines": { + "node": ">=8.x" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/re-resizable": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", + "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-avatar-editor": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/react-avatar-editor/-/react-avatar-editor-13.0.2.tgz", + "integrity": "sha512-a4ajbi7lwDh98kgEtSEeKMu0vs0CHTczkq4Xcxr1EiwMFH1GlgHCEtwGU8q/H5W8SeLnH4KPK8LUjEEaZXklxQ==", + "license": "MIT", + "dependencies": { + "@babel/plugin-transform-runtime": "^7.12.1", + "@babel/runtime": "^7.12.5", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-colorful": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.6.1.tgz", + "integrity": "sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-draggable": { + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.6.tgz", + "integrity": "sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw==", + "license": "MIT", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-draggable/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react-dropzone": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-12.1.0.tgz", + "integrity": "sha512-iBYHA1rbopIvtzokEX4QubO6qk5IF/x3BtKGu74rF2JkQDXnwC4uO/lHKpaw4PJIV6iIAYOlwLv2FpiGyqHNog==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.5.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8" + } + }, + "node_modules/react-error-boundary": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-5.0.0.tgz", + "integrity": "sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" + }, + "node_modules/react-hotkeys-hook": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.6.2.tgz", + "integrity": "sha512-FmP+ZriY3EG59Ug/lxNfrObCnW9xQShgk7Nb83+CkpfkcCpfS95ydv+E9JuXA5cp8KtskU7LGlIARpkc92X22Q==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8.1", + "react-dom": ">=16.8.1" + } + }, + "node_modules/react-i18next": { + "version": "15.5.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.5.2.tgz", + "integrity": "sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0", + "typescript": "^5" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/react-is": { + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", + "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", + "license": "MIT" + }, + "node_modules/react-layout-kit": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/react-layout-kit/-/react-layout-kit-1.9.1.tgz", + "integrity": "sha512-tQO5J+Ajppu2JCdhgFaFbWCg01WJXXaQ5vg8cxzsv8vVeogJKGFgoJm9OI2saDFchfKP3RABd+aRY5vB++poqw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7", + "@emotion/css": "^11" + }, + "peerDependencies": { + "react": ">=18" + } + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-merge-refs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-2.1.1.tgz", + "integrity": "sha512-jLQXJ/URln51zskhgppGJ2ub7b2WFKGq3cl3NYKtlHoTG+dN2q7EzWrn3hN3EgPsTMvpR9tpq5ijdp7YwFZkag==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-rnd": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/react-rnd/-/react-rnd-10.5.2.tgz", + "integrity": "sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw==", + "license": "MIT", + "dependencies": { + "re-resizable": "6.11.2", + "react-draggable": "4.4.6", + "tslib": "2.6.2" + }, + "peerDependencies": { + "react": ">=16.3.0", + "react-dom": ">=16.3.0" + } + }, + "node_modules/react-rnd/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-zoom-pan-pinch": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/react-zoom-pan-pinch/-/react-zoom-pan-pinch-3.7.0.tgz", + "integrity": "sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA==", + "license": "MIT", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "react": "*", + "react-dom": "*" + } + }, + "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==", + "devOptional": true, + "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/recharts": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.6.0.tgz", + "integrity": "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recma-build-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", + "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-build-jsx": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-jsx": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.0.tgz", + "integrity": "sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==", + "license": "MIT", + "dependencies": { + "acorn-jsx": "^5.0.0", + "estree-util-to-js": "^2.0.0", + "recma-parse": "^1.0.0", + "recma-stringify": "^1.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-parse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", + "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "esast-util-from-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/recma-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", + "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-util-to-js": "^2.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-recma": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", + "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "hast-util-to-estree": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-breaks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", + "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-newline-to-break": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-mdx": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", + "integrity": "sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==", + "license": "MIT", + "dependencies": { + "mdast-util-mdx": "^3.0.0", + "micromark-extension-mdxjs": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/request": { + "version": "2.72.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.72.0.tgz", + "integrity": "sha512-rQiQ3Eza3HNC+gBlzKxXaPwG1rQIcO0/7TKGIgA9D/obvFK//H+pzkCS4CctQ7aFk6LboTvyFXHMEdf8P4pSxg==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.6.0", + "aws4": "^1.2.1", + "bl": "~1.1.2", + "caseless": "~0.11.0", + "combined-stream": "~1.0.5", + "extend": "~3.0.0", + "forever-agent": "~0.6.1", + "form-data": "~1.0.0-rc3", + "har-validator": "~2.0.6", + "hawk": "~3.1.3", + "http-signature": "~1.1.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.7", + "node-uuid": "~1.4.7", + "oauth-sign": "~0.8.1", + "qs": "~6.1.0", + "stringstream": "~0.0.4", + "tough-cookie": "~2.2.0", + "tunnel-agent": "~0.4.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/request/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/request/node_modules/bl": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.1.2.tgz", + "integrity": "sha512-uVVYHEQk+OuWvCi5U+iquVXvvGCWXKawjwELIR2XMLsqfV/e2sGDClVBs8OlGIgGsStPRY/Es311YKYIlYCWAg==", + "license": "MIT", + "dependencies": { + "readable-stream": "~2.0.5" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.1.tgz", + "integrity": "sha512-M4Yhq2mLogpCtpUmfopFlTTuIe6mSCTgKvnlMhDj3NcgVhA1uS20jT0n+xunKPzpmL5w2erSVtp+SKiJf1TlWg==", + "license": "MIT", + "dependencies": { + "async": "^2.0.1", + "combined-stream": "^1.0.5", + "mime-types": "^2.1.11" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/request/node_modules/process-nextick-args": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==", + "license": "MIT" + }, + "node_modules/request/node_modules/readable-stream": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "integrity": "sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "string_decoder": "~0.10.x", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/request/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "license": "MIT" + }, + "node_modules/request/node_modules/tunnel-agent": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha512-e0IoVDWx8SDHc/hwFTqJDQ7CCDTEeGhmcT9jkWJjoGQSpgBz20nAMr80E3Tpk7PatJ1b37DQDgJR3CNSzcMOZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "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/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-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/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve/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/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/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "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/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "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==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "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.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/screenfull": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/screenfull/-/screenfull-5.2.0.tgz", + "integrity": "sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/scroll-into-view-if-needed": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", + "integrity": "sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "compute-scroll-into-view": "^3.0.2" + } + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "license": "BSD-3-Clause" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT" + }, + "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-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, + "node_modules/sharp": { + "version": "0.33.1", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.1.tgz", + "integrity": "sha512-iAYUnOdTqqZDb3QjMneBKINTllCJDZ3em6WaWy7NPECM4aHncvqHRm0v0bN9nqJxMiwamv5KIdauJ6lUzKDpTQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "semver": "^7.5.4" + }, + "engines": { + "libvips": ">=8.15.0", + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.1", + "@img/sharp-darwin-x64": "0.33.1", + "@img/sharp-libvips-darwin-arm64": "1.0.0", + "@img/sharp-libvips-darwin-x64": "1.0.0", + "@img/sharp-libvips-linux-arm": "1.0.0", + "@img/sharp-libvips-linux-arm64": "1.0.0", + "@img/sharp-libvips-linux-s390x": "1.0.0", + "@img/sharp-libvips-linux-x64": "1.0.0", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.0", + "@img/sharp-libvips-linuxmusl-x64": "1.0.0", + "@img/sharp-linux-arm": "0.33.1", + "@img/sharp-linux-arm64": "0.33.1", + "@img/sharp-linux-s390x": "0.33.1", + "@img/sharp-linux-x64": "0.33.1", + "@img/sharp-linuxmusl-arm64": "0.33.1", + "@img/sharp-linuxmusl-x64": "0.33.1", + "@img/sharp-wasm32": "0.33.1", + "@img/sharp-win32-ia32": "0.33.1", + "@img/sharp-win32-x64": "0.33.1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shiki": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.6.0.tgz", + "integrity": "sha512-tKn/Y0MGBTffQoklaATXmTqDU02zx8NYBGQ+F6gy87/YjKbizcLd+Cybh/0ZtOBX9r1NEnAy/GTRDKtOsc1L9w==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.6.0", + "@shikijs/engine-javascript": "3.6.0", + "@shikijs/engine-oniguruma": "3.6.0", + "@shikijs/langs": "3.6.0", + "@shikijs/themes": "3.6.0", + "@shikijs/types": "3.6.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "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==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "devOptional": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "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": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "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-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/sntp": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", + "integrity": "sha512-7bgVOAnPj3XjrKY577S+puCKGCRlUrcrEdsMeRXlg9Ghf5df/xNi6sONUa43WrHUd3TjJBF7O04jYoiY0FVa0A==", + "deprecated": "This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues.", + "dependencies": { + "hoek": "2.x.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/sonner": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.5.tgz", + "integrity": "sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "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/source-map-support/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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/spawn-command": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", + "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", + "dev": true + }, + "node_modules/split-on-first": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz", + "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split-string/node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "license": "MIT", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, + "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/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/stream-meter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stream-meter/-/stream-meter-1.0.4.tgz", + "integrity": "sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "^2.1.4" + } + }, + "node_modules/stream-meter/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", + "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/stream-meter/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" + }, + "node_modules/stream-meter/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", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-convert": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", + "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", + "license": "MIT", + "peer": true + }, + "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==", + "devOptional": true, + "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==", + "dev": true, + "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/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/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==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/string-width/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==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", + "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" + }, + "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/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "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/swr": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.3.tgz", + "integrity": "sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==", + "license": "MIT" + }, + "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==", + "devOptional": 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-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "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", + "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/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/temp-file/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/temp-file/node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/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/text-extensions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-2.4.0.tgz", + "integrity": "sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.22" + } + }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "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" + }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.1.tgz", + "integrity": "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==", + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "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-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toggle-selection": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz", + "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", + "license": "MIT", + "peer": true + }, + "node_modules/tough-cookie": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.2.2.tgz", + "integrity": "sha512-Knz9Yr0hlBoWQgUKzOIvRg5adinizAf49i2gHRhj6cLjlM304zRw7uyiY22ADniDxnPHXfIeyQD0EAkgpIz0ow==", + "deprecated": "ReDoS vulnerability parsing Set-Cookie https://nodesecurity.io/advisories/130", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/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-md5": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.3.1.tgz", + "integrity": "sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/turndown": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz", + "integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==", + "license": "MIT", + "dependencies": { + "@mixmark-io/domino": "^2.2.0" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "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/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-remove-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", + "integrity": "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/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/unpdf": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/unpdf/-/unpdf-0.12.2.tgz", + "integrity": "sha512-3eyDFfayk+Sf5+inJ4OyhecR2BtRFEeZqUfGPdq2O8aBLau9MYL9lAP+GEcSAaVd2JWqde8Dnz38z0x7KRglaA==", + "license": "MIT", + "optionalDependencies": { + "canvas": "^2.11.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "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/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ==", + "license": "MIT", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url-join": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz", + "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "license": "MIT" + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-merge-value": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-merge-value/-/use-merge-value-1.2.0.tgz", + "integrity": "sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.x" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "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/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8n": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/v8n/-/v8n-1.5.1.tgz", + "integrity": "sha512-LdabyT4OffkyXFCe9UT+uMkxNBs5rcTVuZClvxQr08D5TUgo1OFKkoT65qYRCsiKBl/usHjpXvP4hHMzzDRj3A==", + "license": "MIT" + }, + "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/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, + "node_modules/wait-on": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-7.2.0.tgz", + "integrity": "sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "axios": "^1.6.1", + "joi": "^17.11.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "rxjs": "^7.8.1" + }, + "bin": { + "wait-on": "bin/wait-on" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "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==", + "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/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "license": "ISC" + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "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/xmldom": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz", + "integrity": "sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/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==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, + "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/yocto-queue": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.1.tgz", + "integrity": "sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zhipu-ai-provider": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/zhipu-ai-provider/-/zhipu-ai-provider-0.1.1.tgz", + "integrity": "sha512-cVwvvGtPiQqgsGdBzHCHC5oQ7z6slEQTbXJ5+42gQGX4N5uRUvYj+YYLp7Cr1HPQGF3zR2p8vNbT5etPHD4NbA==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.9", + "@ai-sdk/provider-utils": "2.1.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + } + }, + "node_modules/zhipu-ai-provider/node_modules/@ai-sdk/provider": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-1.0.9.tgz", + "integrity": "sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA==", + "license": "Apache-2.0", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/zhipu-ai-provider/node_modules/@ai-sdk/provider-utils": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-2.1.10.tgz", + "integrity": "sha512-4GZ8GHjOFxePFzkl3q42AU0DQOtTQ5w09vmaWUf/pKFXJPizlnzKSUkF0f+VkapIUfDugyMqPMT1ge8XQzVI7Q==", + "license": "Apache-2.0", + "dependencies": { + "@ai-sdk/provider": "1.0.9", + "eventsource-parser": "^3.0.0", + "nanoid": "^3.3.8", + "secure-json-parse": "^2.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/zhipu-ai-provider/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/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/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + }, + "node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/easy-dataset-main/package.json b/easy-dataset-main/package.json new file mode 100644 index 0000000..595a55a --- /dev/null +++ b/easy-dataset-main/package.json @@ -0,0 +1,184 @@ +{ + "name": "easy-dataset", + "version": "1.7.2", + "private": true, + "author": { + "name": "ConardLi", + "email": "1009903985@qq.com", + "url": "https://github.com/ConardLi" + }, + "homepage": "https://github.com/ConardLi/easy-dataset", + "scripts": { + "db:studio": "prisma studio", + "db:push": "prisma db push", + "db:template": "node prisma/generate-template.js", + "dev": "prisma db push && next dev -p 1717", + "build": "prisma db push && next build", + "start": "next start -p 1717", + "lint": "next lint", + "electron": "electron .", + "electron-dev": "concurrently \"pnpm dev\" \"wait-on http://localhost:1717 && electron .\"", + "electron-pack": "electron-builder --dir", + "electron-dist": "electron-builder", + "clean-dist": "rm -rf dist", + "electron-build": "pnpm clean-dist && pnpm db:template && prisma db push && next build && electron-builder -mwl", + "electron-build-mac": "pnpm clean-dist && pnpm db:template && prisma db push && next build && electron-builder --mac", + "electron-build-win": "pnpm clean-dist && pnpm db:template && prisma db push && next build && electron-builder --win", + "electron-build-linux": "pnpm clean-dist && pnpm db:template && prisma db push && next build && electron-builder --linux", + "docker": "docker build -t easy-dataset .", + "prettier": "npx prettier --write ." + }, + "bin": "desktop/server.js", + "pkg": { + "assets": [ + ".next/**/*", + "public/**/*", + "locales/**/*", + "package.json", + "node_modules/next/**/*" + ], + "targets": [ + "node18-macos-arm64", + "node18-macos-x64", + "node18-win-x64", + "node18-linux-x64" + ], + "outputPath": "dist" + }, + "dependencies": { + "@ai-sdk/openai": "^1.3.9", + "@ai-sdk/openai-compatible": "^1.0.22", + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", + "@fontsource/inter": "^5.0.16", + "@fontsource/jetbrains-mono": "^5.0.18", + "@huggingface/hub": "^2.0.2", + "@lobehub/icons": "^1.96.0", + "@mui/icons-material": "5.16.14", + "@mui/lab": "5.0.0-alpha.175", + "@mui/material": "5.16.14", + "@opendocsg/pdf2md": "^0.2.1", + "@openrouter/ai-sdk-provider": "^0.4.5", + "@prisma/client": "^6.6.0", + "adm-zip": "^0.5.16", + "ai": "^4.3.4", + "axios": "^1.8.4", + "electron-build": "^0.0.3", + "electron-updater": "^6.3.9", + "formidable": "^3.5.2", + "framer-motion": "^12.4.10", + "github-markdown-css": "^5.8.1", + "i18next": "^24.2.2", + "i18next-browser-languagedetector": "^8.0.4", + "image-size": "^2.0.2", + "jotai": "^2.12.3", + "jsonrepair": "^3.13.1", + "jszip": "^3.10.1", + "langchain": "^0.3.24", + "mammoth": "^1.9.0", + "nanoid": "^5.1.5", + "next": "^14.2.29", + "next-themes": "^0.2.1", + "ollama-ai-provider": "^1.2.0", + "opener": "^1.5.2", + "pdf2md-js": "1.0.8", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-i18next": "^15.4.1", + "react-markdown": "^10.0.1", + "recharts": "^3.6.0", + "sharp": "^0.33.1", + "sonner": "^2.0.3", + "turndown": "^7.2.0", + "xlsx": "^0.18.5", + "xmldom": "^0.6.0", + "zhipu-ai-provider": "^0.1.1", + "zod": "^3.25.76" + }, + "license": "AGPL 3.0", + "devDependencies": { + "@commitlint/cli": "^19.8.0", + "@commitlint/config-conventional": "^19.8.0", + "concurrently": "^8.2.2", + "electron": "^35.0.0", + "electron-builder": "^24.13.3", + "husky": "^9.1.7", + "lint-staged": "15.5.2", + "pkg": "^5.8.1", + "prisma": "^6.6.0", + "wait-on": "^7.2.0" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx,json,md}": "npm run prettier" + }, + "main": "electron/main.js", + "description": "一个用于创建大模型微调数据集的应用程序", + "build": { + "appId": "com.easydataset.app", + "productName": "Easy Dataset", + "files": [ + ".next/**/*", + "!.next/cache/**/*", + "public/**/*", + "locales/**/*", + "package.json", + "electron/**/*", + "node_modules/**/*", + "!node_modules/.cache/**/*", + "!node_modules/.bin/**/*", + "!node_modules/.vite/**/*", + "!**/*.{md,d.ts,map}", + "!**/node_modules/*/{CHANGELOG.md,README.md,README,readme.md,readme}" + ], + "extraResources": [ + "prisma/schema.prisma", + "prisma/template.sqlite", + "prisma/sql.json", + "node_modules/.prisma/**/*", + "node_modules/@prisma/client/**/*" + ], + "directories": { + "buildResources": "public", + "output": "dist" + }, + "asar": true, + "asarUnpack": [ + "**/node_modules/sharp/**/*", + "**/node_modules/@img/**/*" + ], + "compression": "maximum", + "mac": { + "icon": "public/imgs/logo.icns", + "category": "public.app-category.developer-tools", + "target": [ + { + "target": "dmg", + "arch": [ + "arm64", + "x64" + ] + } + ], + "electronLanguages": [ + "zh_CN", + "en" + ] + }, + "win": { + "icon": "public/imgs/logo.ico", + "target": [ + { + "target": "nsis", + "arch": [ + "x64" + ] + } + ] + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "perMachine": false + } + } +} diff --git a/easy-dataset-main/pnpm-lock.yaml b/easy-dataset-main/pnpm-lock.yaml new file mode 100644 index 0000000..ed78411 --- /dev/null +++ b/easy-dataset-main/pnpm-lock.yaml @@ -0,0 +1,13492 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + .: + dependencies: + '@ai-sdk/openai': + specifier: ^1.3.9 + version: 1.3.22(zod@3.25.76) + '@ai-sdk/openai-compatible': + specifier: ^1.0.22 + version: 1.0.22(zod@3.25.76) + '@emotion/react': + specifier: ^11.11.3 + version: 11.14.0(@types/react@19.1.8)(react@18.3.1) + '@emotion/styled': + specifier: ^11.11.0 + version: 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + '@fontsource/inter': + specifier: ^5.0.16 + version: 5.2.6 + '@fontsource/jetbrains-mono': + specifier: ^5.0.18 + version: 5.2.6 + '@huggingface/hub': + specifier: ^2.0.2 + version: 2.2.0 + '@lobehub/icons': + specifier: ^1.96.0 + version: 1.98.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/icons-material': + specifier: 5.16.14 + version: 5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + '@mui/lab': + specifier: 5.0.0-alpha.175 + version: 5.0.0-alpha.175(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/material': + specifier: 5.16.14 + version: 5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@opendocsg/pdf2md': + specifier: ^0.2.1 + version: 0.2.1 + '@openrouter/ai-sdk-provider': + specifier: ^0.4.5 + version: 0.4.6(zod@3.25.76) + '@prisma/client': + specifier: ^6.6.0 + version: 6.9.0(prisma@6.9.0(typescript@5.8.3))(typescript@5.8.3) + adm-zip: + specifier: ^0.5.16 + version: 0.5.16 + ai: + specifier: ^4.3.4 + version: 4.3.16(react@18.3.1)(zod@3.25.76) + axios: + specifier: ^1.8.4 + version: 1.9.0 + electron-build: + specifier: ^0.0.3 + version: 0.0.3 + electron-updater: + specifier: ^6.3.9 + version: 6.6.2 + formidable: + specifier: ^3.5.2 + version: 3.5.4 + framer-motion: + specifier: ^12.4.10 + version: 12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + github-markdown-css: + specifier: ^5.8.1 + version: 5.8.1 + i18next: + specifier: ^24.2.2 + version: 24.2.3(typescript@5.8.3) + i18next-browser-languagedetector: + specifier: ^8.0.4 + version: 8.2.0 + image-size: + specifier: ^2.0.2 + version: 2.0.2 + jotai: + specifier: ^2.12.3 + version: 2.12.5(@types/react@19.1.8)(react@18.3.1) + jsonrepair: + specifier: ^3.13.1 + version: 3.13.1 + jszip: + specifier: ^3.10.1 + version: 3.10.1 + langchain: + specifier: ^0.3.24 + version: 0.3.28(@langchain/core@0.3.58(openai@4.104.0(zod@3.25.76)))(axios@1.9.0)(openai@4.104.0(zod@3.25.76)) + mammoth: + specifier: ^1.9.0 + version: 1.9.1 + nanoid: + specifier: ^5.1.5 + version: 5.1.5 + next: + specifier: ^14.2.29 + version: 14.2.29(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes: + specifier: ^0.2.1 + version: 0.2.1(next@14.2.29(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ollama-ai-provider: + specifier: ^1.2.0 + version: 1.2.0(zod@3.25.76) + opener: + specifier: ^1.5.2 + version: 1.5.2 + pdf2md-js: + specifier: 1.0.8 + version: 1.0.8 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + react-i18next: + specifier: ^15.4.1 + version: 15.5.2(i18next@24.2.3(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3) + react-markdown: + specifier: ^10.0.1 + version: 10.1.0(@types/react@19.1.8)(react@18.3.1) + recharts: + specifier: ^3.6.0 + version: 3.6.0(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react-is@19.1.0)(react@18.3.1)(redux@5.0.1) + sharp: + specifier: ^0.33.1 + version: 0.33.5 + sonner: + specifier: ^2.0.3 + version: 2.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + turndown: + specifier: ^7.2.0 + version: 7.2.0 + xlsx: + specifier: ^0.18.5 + version: 0.18.5 + xmldom: + specifier: ^0.6.0 + version: 0.6.0 + zhipu-ai-provider: + specifier: ^0.1.1 + version: 0.1.1(zod@3.25.76) + zod: + specifier: ^3.25.76 + version: 3.25.76 + devDependencies: + '@commitlint/cli': + specifier: ^19.8.0 + version: 19.8.1(@types/node@24.0.1)(typescript@5.8.3) + '@commitlint/config-conventional': + specifier: ^19.8.0 + version: 19.8.1 + concurrently: + specifier: ^8.2.2 + version: 8.2.2 + electron: + specifier: ^35.0.0 + version: 35.5.1 + electron-builder: + specifier: ^24.13.3 + version: 24.13.3(electron-builder-squirrel-windows@24.13.3) + husky: + specifier: ^9.1.7 + version: 9.1.7 + lint-staged: + specifier: 15.5.2 + version: 15.5.2 + pkg: + specifier: ^5.8.1 + version: 5.8.1 + prisma: + specifier: ^6.6.0 + version: 6.9.0(typescript@5.8.3) + wait-on: + specifier: ^7.2.0 + version: 7.2.0 + +packages: + 7zip-bin@5.2.0: + resolution: + { integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A== } + + '@ai-sdk/openai-compatible@1.0.22': + resolution: + { integrity: sha512-Q+lwBIeMprc/iM+vg1yGjvzRrp74l316wDpqWdbmd4VXXlllblzGsUgBLTeKvcEapFTgqk0FRETvSb58Y6dsfA== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/openai@1.3.22': + resolution: + { integrity: sha512-QwA+2EkG0QyjVR+7h6FE7iOu2ivNqAVMm9UJZkVxxTk5OIq5fFJDTEI/zICEMuHImTTXR2JjsL6EirJ28Jc4cw== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.0.0 + + '@ai-sdk/provider-utils@2.1.10': + resolution: + { integrity: sha512-4GZ8GHjOFxePFzkl3q42AU0DQOtTQ5w09vmaWUf/pKFXJPizlnzKSUkF0f+VkapIUfDugyMqPMT1ge8XQzVI7Q== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/provider-utils@2.2.8': + resolution: + { integrity: sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.23.8 + + '@ai-sdk/provider-utils@3.0.12': + resolution: + { integrity: sha512-ZtbdvYxdMoria+2SlNarEk6Hlgyf+zzcznlD55EAl+7VZvJaSg2sqPvwArY7L6TfDEDJsnCq0fdhBSkYo0Xqdg== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.25.76 || ^4.1.8 + + '@ai-sdk/provider@1.0.9': + resolution: + { integrity: sha512-jie6ZJT2ZR0uVOVCDc9R2xCX5I/Dum/wEK28lx21PJx6ZnFAN9EzD2WsPhcDWfCgGx3OAZZ0GyM3CEobXpa9LA== } + engines: { node: '>=18' } + + '@ai-sdk/provider@1.1.3': + resolution: + { integrity: sha512-qZMxYJ0qqX/RfnuIaab+zp8UAeJn/ygXXAffR5I4N0n1IrvA6qBsjc8hXLmBiMV2zoXlifkacF7sEFnYnjBcqg== } + engines: { node: '>=18' } + + '@ai-sdk/provider@2.0.0': + resolution: + { integrity: sha512-6o7Y2SeO9vFKB8lArHXehNuusnpddKPk7xqL7T2/b+OvXMRIXUO1rR4wcv1hAFUAT9avGZshty3Wlua/XA7TvA== } + engines: { node: '>=18' } + + '@ai-sdk/react@1.2.12': + resolution: + { integrity: sha512-jK1IZZ22evPZoQW3vlkZ7wvjYGYF+tRBKXtrcolduIkQ/m/sOAVcVeVDUDvh1T91xCnWCdUGCPZg2avZ90mv3g== } + engines: { node: '>=18' } + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + zod: + optional: true + + '@ai-sdk/ui-utils@1.2.11': + resolution: + { integrity: sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.23.8 + + '@ampproject/remapping@2.3.0': + resolution: + { integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== } + engines: { node: '>=6.0.0' } + + '@ant-design/colors@7.2.1': + resolution: + { integrity: sha512-lCHDcEzieu4GA3n8ELeZ5VQ8pKQAWcGGLRTQ50aQM2iqPpq2evTxER84jfdPvsPAtEcZ7m44NI45edFMo8oOYQ== } + + '@ant-design/cssinjs-utils@1.1.3': + resolution: + { integrity: sha512-nOoQMLW1l+xR1Co8NFVYiP8pZp3VjIIzqV6D6ShYF2ljtdwWJn5WSsH+7kvCktXL/yhEtWURKOfH5Xz/gzlwsg== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@ant-design/cssinjs@1.23.0': + resolution: + { integrity: sha512-7GAg9bD/iC9ikWatU9ym+P9ugJhi/WbsTWzcKN6T4gU0aehsprtke1UAaaSxxkjjmkJb3llet/rbUSLPgwlY4w== } + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/fast-color@2.0.6': + resolution: + { integrity: sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA== } + engines: { node: '>=8.x' } + + '@ant-design/icons-svg@4.4.2': + resolution: + { integrity: sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA== } + + '@ant-design/icons@5.6.1': + resolution: + { integrity: sha512-0/xS39c91WjPAZOWsvi1//zjx6kAp4kxWwctR6kuU6p133w8RU0D2dSCvZC19uQyharg/sAvYxGYWl01BbZZfg== } + engines: { node: '>=8' } + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + '@ant-design/react-slick@1.1.2': + resolution: + { integrity: sha512-EzlvzE6xQUBrZuuhSAFTdsr4P2bBBHGZwKFemEfq8gIGyIQCxalYfZW/T2ORbtQx5rU69o+WycP3exY/7T1hGA== } + peerDependencies: + react: '>=16.9.0' + + '@antfu/install-pkg@1.1.0': + resolution: + { integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ== } + + '@antfu/utils@8.1.1': + resolution: + { integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ== } + + '@babel/code-frame@7.27.1': + resolution: + { integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg== } + engines: { node: '>=6.9.0' } + + '@babel/compat-data@7.27.5': + resolution: + { integrity: sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg== } + engines: { node: '>=6.9.0' } + + '@babel/core@7.27.4': + resolution: + { integrity: sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g== } + engines: { node: '>=6.9.0' } + + '@babel/generator@7.18.2': + resolution: + { integrity: sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw== } + engines: { node: '>=6.9.0' } + + '@babel/generator@7.27.5': + resolution: + { integrity: sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw== } + engines: { node: '>=6.9.0' } + + '@babel/helper-compilation-targets@7.27.2': + resolution: + { integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ== } + engines: { node: '>=6.9.0' } + + '@babel/helper-define-polyfill-provider@0.6.4': + resolution: + { integrity: sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw== } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-module-imports@7.27.1': + resolution: + { integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w== } + engines: { node: '>=6.9.0' } + + '@babel/helper-module-transforms@7.27.3': + resolution: + { integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg== } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: + { integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw== } + engines: { node: '>=6.9.0' } + + '@babel/helper-string-parser@7.27.1': + resolution: + { integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA== } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-identifier@7.27.1': + resolution: + { integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow== } + engines: { node: '>=6.9.0' } + + '@babel/helper-validator-option@7.27.1': + resolution: + { integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg== } + engines: { node: '>=6.9.0' } + + '@babel/helpers@7.27.6': + resolution: + { integrity: sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug== } + engines: { node: '>=6.9.0' } + + '@babel/parser@7.18.4': + resolution: + { integrity: sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== } + engines: { node: '>=6.0.0' } + hasBin: true + + '@babel/parser@7.27.5': + resolution: + { integrity: sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg== } + engines: { node: '>=6.0.0' } + hasBin: true + + '@babel/plugin-transform-runtime@7.27.4': + resolution: + { integrity: sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A== } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.6': + resolution: + { integrity: sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q== } + engines: { node: '>=6.9.0' } + + '@babel/template@7.27.2': + resolution: + { integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw== } + engines: { node: '>=6.9.0' } + + '@babel/traverse@7.27.4': + resolution: + { integrity: sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA== } + engines: { node: '>=6.9.0' } + + '@babel/types@7.19.0': + resolution: + { integrity: sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== } + engines: { node: '>=6.9.0' } + + '@babel/types@7.27.6': + resolution: + { integrity: sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q== } + engines: { node: '>=6.9.0' } + + '@braintree/sanitize-url@7.1.1': + resolution: + { integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw== } + + '@cfworker/json-schema@4.1.1': + resolution: + { integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og== } + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: + { integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ== } + + '@chevrotain/gast@11.0.3': + resolution: + { integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q== } + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: + { integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA== } + + '@chevrotain/types@11.0.3': + resolution: + { integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ== } + + '@chevrotain/utils@11.0.3': + resolution: + { integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ== } + + '@commitlint/cli@19.8.1': + resolution: + { integrity: sha512-LXUdNIkspyxrlV6VDHWBmCZRtkEVRpBKxi2Gtw3J54cGWhLCTouVD/Q6ZSaSvd2YaDObWK8mDjrz3TIKtaQMAA== } + engines: { node: '>=v18' } + hasBin: true + + '@commitlint/config-conventional@19.8.1': + resolution: + { integrity: sha512-/AZHJL6F6B/G959CsMAzrPKKZjeEiAVifRyEwXxcT6qtqbPwGw+iQxmNS+Bu+i09OCtdNRW6pNpBvgPrtMr9EQ== } + engines: { node: '>=v18' } + + '@commitlint/config-validator@19.8.1': + resolution: + { integrity: sha512-0jvJ4u+eqGPBIzzSdqKNX1rvdbSU1lPNYlfQQRIFnBgLy26BtC0cFnr7c/AyuzExMxWsMOte6MkTi9I3SQ3iGQ== } + engines: { node: '>=v18' } + + '@commitlint/ensure@19.8.1': + resolution: + { integrity: sha512-mXDnlJdvDzSObafjYrOSvZBwkD01cqB4gbnnFuVyNpGUM5ijwU/r/6uqUmBXAAOKRfyEjpkGVZxaDsCVnHAgyw== } + engines: { node: '>=v18' } + + '@commitlint/execute-rule@19.8.1': + resolution: + { integrity: sha512-YfJyIqIKWI64Mgvn/sE7FXvVMQER/Cd+s3hZke6cI1xgNT/f6ZAz5heND0QtffH+KbcqAwXDEE1/5niYayYaQA== } + engines: { node: '>=v18' } + + '@commitlint/format@19.8.1': + resolution: + { integrity: sha512-kSJj34Rp10ItP+Eh9oCItiuN/HwGQMXBnIRk69jdOwEW9llW9FlyqcWYbHPSGofmjsqeoxa38UaEA5tsbm2JWw== } + engines: { node: '>=v18' } + + '@commitlint/is-ignored@19.8.1': + resolution: + { integrity: sha512-AceOhEhekBUQ5dzrVhDDsbMaY5LqtN8s1mqSnT2Kz1ERvVZkNihrs3Sfk1Je/rxRNbXYFzKZSHaPsEJJDJV8dg== } + engines: { node: '>=v18' } + + '@commitlint/lint@19.8.1': + resolution: + { integrity: sha512-52PFbsl+1EvMuokZXLRlOsdcLHf10isTPlWwoY1FQIidTsTvjKXVXYb7AvtpWkDzRO2ZsqIgPK7bI98x8LRUEw== } + engines: { node: '>=v18' } + + '@commitlint/load@19.8.1': + resolution: + { integrity: sha512-9V99EKG3u7z+FEoe4ikgq7YGRCSukAcvmKQuTtUyiYPnOd9a2/H9Ak1J9nJA1HChRQp9OA/sIKPugGS+FK/k1A== } + engines: { node: '>=v18' } + + '@commitlint/message@19.8.1': + resolution: + { integrity: sha512-+PMLQvjRXiU+Ae0Wc+p99EoGEutzSXFVwQfa3jRNUZLNW5odZAyseb92OSBTKCu+9gGZiJASt76Cj3dLTtcTdg== } + engines: { node: '>=v18' } + + '@commitlint/parse@19.8.1': + resolution: + { integrity: sha512-mmAHYcMBmAgJDKWdkjIGq50X4yB0pSGpxyOODwYmoexxxiUCy5JJT99t1+PEMK7KtsCtzuWYIAXYAiKR+k+/Jw== } + engines: { node: '>=v18' } + + '@commitlint/read@19.8.1': + resolution: + { integrity: sha512-03Jbjb1MqluaVXKHKRuGhcKWtSgh3Jizqy2lJCRbRrnWpcM06MYm8th59Xcns8EqBYvo0Xqb+2DoZFlga97uXQ== } + engines: { node: '>=v18' } + + '@commitlint/resolve-extends@19.8.1': + resolution: + { integrity: sha512-GM0mAhFk49I+T/5UCYns5ayGStkTt4XFFrjjf0L4S26xoMTSkdCf9ZRO8en1kuopC4isDFuEm7ZOm/WRVeElVg== } + engines: { node: '>=v18' } + + '@commitlint/rules@19.8.1': + resolution: + { integrity: sha512-Hnlhd9DyvGiGwjfjfToMi1dsnw1EXKGJNLTcsuGORHz6SS9swRgkBsou33MQ2n51/boIDrbsg4tIBbRpEWK2kw== } + engines: { node: '>=v18' } + + '@commitlint/to-lines@19.8.1': + resolution: + { integrity: sha512-98Mm5inzbWTKuZQr2aW4SReY6WUukdWXuZhrqf1QdKPZBCCsXuG87c+iP0bwtD6DBnmVVQjgp4whoHRVixyPBg== } + engines: { node: '>=v18' } + + '@commitlint/top-level@19.8.1': + resolution: + { integrity: sha512-Ph8IN1IOHPSDhURCSXBz44+CIu+60duFwRsg6HqaISFHQHbmBtxVw4ZrFNIYUzEP7WwrNPxa2/5qJ//NK1FGcw== } + engines: { node: '>=v18' } + + '@commitlint/types@19.8.1': + resolution: + { integrity: sha512-/yCrWGCoA1SVKOks25EGadP9Pnj0oAIHGpl2wH2M2Y46dPM2ueb8wyCVOD7O3WCTkaJ0IkKvzhl1JY7+uCT2Dw== } + engines: { node: '>=v18' } + + '@develar/schema-utils@2.6.5': + resolution: + { integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig== } + engines: { node: '>= 8.9.0' } + + '@dnd-kit/accessibility@3.1.1': + resolution: + { integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw== } + peerDependencies: + react: '>=16.8.0' + + '@dnd-kit/core@6.3.1': + resolution: + { integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ== } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@dnd-kit/modifiers@9.0.0': + resolution: + { integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw== } + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/sortable@10.0.0': + resolution: + { integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg== } + peerDependencies: + '@dnd-kit/core': ^6.3.0 + react: '>=16.8.0' + + '@dnd-kit/utilities@3.2.2': + resolution: + { integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg== } + peerDependencies: + react: '>=16.8.0' + + '@electron/asar@3.4.1': + resolution: + { integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA== } + engines: { node: '>=10.12.0' } + hasBin: true + + '@electron/get@2.0.3': + resolution: + { integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ== } + engines: { node: '>=12' } + + '@electron/notarize@2.2.1': + resolution: + { integrity: sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg== } + engines: { node: '>= 10.0.0' } + + '@electron/osx-sign@1.0.5': + resolution: + { integrity: sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww== } + engines: { node: '>=12.0.0' } + hasBin: true + + '@electron/universal@1.5.1': + resolution: + { integrity: sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw== } + engines: { node: '>=8.6' } + + '@emnapi/runtime@1.4.3': + resolution: + { integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ== } + + '@emoji-mart/data@1.2.1': + resolution: + { integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw== } + + '@emoji-mart/react@1.1.1': + resolution: + { integrity: sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g== } + peerDependencies: + emoji-mart: ^5.2 + react: ^16.8 || ^17 || ^18 + + '@emotion/babel-plugin@11.13.5': + resolution: + { integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ== } + + '@emotion/cache@11.14.0': + resolution: + { integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA== } + + '@emotion/css@11.13.5': + resolution: + { integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w== } + + '@emotion/hash@0.8.0': + resolution: + { integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== } + + '@emotion/hash@0.9.2': + resolution: + { integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== } + + '@emotion/is-prop-valid@1.3.1': + resolution: + { integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw== } + + '@emotion/memoize@0.9.0': + resolution: + { integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== } + + '@emotion/react@11.14.0': + resolution: + { integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA== } + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/serialize@1.3.3': + resolution: + { integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA== } + + '@emotion/sheet@1.4.0': + resolution: + { integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== } + + '@emotion/styled@11.14.0': + resolution: + { integrity: sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA== } + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + + '@emotion/unitless@0.10.0': + resolution: + { integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg== } + + '@emotion/unitless@0.7.5': + resolution: + { integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== } + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0': + resolution: + { integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg== } + peerDependencies: + react: '>=16.8.0' + + '@emotion/utils@1.4.2': + resolution: + { integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA== } + + '@emotion/weak-memoize@0.4.0': + resolution: + { integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== } + + '@floating-ui/core@0.7.3': + resolution: + { integrity: sha512-buc8BXHmG9l82+OQXOFU3Kr2XQx9ys01U/Q9HMIrZ300iLc8HLMgh7dcCqgYzAzf4BkoQvDcXf5Y+CuEZ5JBYg== } + + '@floating-ui/core@1.7.1': + resolution: + { integrity: sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw== } + + '@floating-ui/dom@0.5.4': + resolution: + { integrity: sha512-419BMceRLq0RrmTSDxn8hf9R3VCJv2K9PUfugh5JyEFmdjzDo+e8U5EdR8nzKq8Yj1htzLm3b6eQEEam3/rrtg== } + + '@floating-ui/dom@1.7.1': + resolution: + { integrity: sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ== } + + '@floating-ui/react-dom@0.7.2': + resolution: + { integrity: sha512-1T0sJcpHgX/u4I1OzIEhlcrvkUN8ln39nz7fMoE/2HDHrPiMFoOGR7++GYyfUmIQHkkrTinaeQsO3XWubjSvGg== } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react-dom@2.1.3': + resolution: + { integrity: sha512-huMBfiU9UnQ2oBwIhgzyIiSpVgvlDstU8CX0AF+wS+KzmYMs0J2a3GwuFHV1Lz+jlrQGeC1fF+Nv0QoumyV0bA== } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.27.12': + resolution: + { integrity: sha512-kKlWNrpIQxF1B/a2MZvE0/uyKby4960yjO91W7nVyNKmmfNi62xU9HCjL1M1eWzx/LFj/VPSwJVbwQk9Pq/68A== } + peerDependencies: + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@floating-ui/utils@0.2.9': + resolution: + { integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg== } + + '@fontsource/inter@5.2.6': + resolution: + { integrity: sha512-CZs9S1CrjD0jPwsNy9W6j0BhsmRSQrgwlTNkgQXTsAeDRM42LBRLo3eo9gCzfH4GvV7zpyf78Ozfl773826csw== } + + '@fontsource/jetbrains-mono@5.2.6': + resolution: + { integrity: sha512-nz//dBr99hXZmHp10wgNI00qThWImkzRR5PQjvRM+rpmuHO5rYBJCqPPWufidCvmkkryXx/GOP/lgqsM3R3Org== } + + '@giscus/react@3.1.0': + resolution: + { integrity: sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg== } + peerDependencies: + react: ^16 || ^17 || ^18 || ^19 + react-dom: ^16 || ^17 || ^18 || ^19 + + '@hapi/hoek@9.3.0': + resolution: + { integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ== } + + '@hapi/topo@5.1.0': + resolution: + { integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== } + + '@huggingface/hub@2.2.0': + resolution: + { integrity: sha512-G+VS1eMp80KovIHBlsiEigS6I6qmI4j+VQ1UZ8CaXT+pw2A7tj6e/crfxFdKNE2uOK5oQkRFiCBJykMwrWQ8OA== } + engines: { node: '>=18' } + hasBin: true + + '@huggingface/tasks@0.19.15': + resolution: + { integrity: sha512-L4wB/iolKtsErke5yniXXNsGrSuaFmyREpcD4hL/wJox2UKtSEV5gE5gNrlvNaRLBOY41yN7/QmBF4y9byTM6Q== } + + '@hyzyla/pdfium@2.1.7': + resolution: + { integrity: sha512-GQ0mxuVY15RHAyxVRC8IUREAeuhZ+Efab8czhvwiw3mveNmBQAtaV3x2O70NaQlu5juzaP0DXK3Jq8bh6Jgjjw== } + + '@iconify/types@2.0.0': + resolution: + { integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== } + + '@iconify/utils@2.3.0': + resolution: + { integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA== } + + '@img/sharp-darwin-arm64@0.33.5': + resolution: + { integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.33.5': + resolution: + { integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.0.4': + resolution: + { integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== } + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.0.4': + resolution: + { integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== } + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.0.4': + resolution: + { integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== } + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.0.5': + resolution: + { integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== } + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.0.4': + resolution: + { integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA== } + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.0.4': + resolution: + { integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== } + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + resolution: + { integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== } + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + resolution: + { integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== } + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.33.5': + resolution: + { integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.33.5': + resolution: + { integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm] + os: [linux] + + '@img/sharp-linux-s390x@0.33.5': + resolution: + { integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.33.5': + resolution: + { integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.33.5': + resolution: + { integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.33.5': + resolution: + { integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.33.5': + resolution: + { integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [wasm32] + + '@img/sharp-win32-ia32@0.33.5': + resolution: + { integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.33.5': + resolution: + { integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: + { integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== } + engines: { node: '>=12' } + + '@jridgewell/gen-mapping@0.3.8': + resolution: + { integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA== } + engines: { node: '>=6.0.0' } + + '@jridgewell/resolve-uri@3.1.2': + resolution: + { integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== } + engines: { node: '>=6.0.0' } + + '@jridgewell/set-array@1.2.1': + resolution: + { integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== } + engines: { node: '>=6.0.0' } + + '@jridgewell/sourcemap-codec@1.5.0': + resolution: + { integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== } + + '@jridgewell/trace-mapping@0.3.25': + resolution: + { integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== } + + '@langchain/core@0.3.58': + resolution: + { integrity: sha512-HLkOtVofgBHefaUae/+2fLNkpMLzEjHSavTmUF0YC7bDa5NPIZGlP80CGrSFXAeJ+WCPd8rIK8K/p6AW94inUQ== } + engines: { node: '>=18' } + + '@langchain/openai@0.5.13': + resolution: + { integrity: sha512-t5UsO7XYE+DBQlXQ21QK74Y+LH4It20wnENrmueNvxIWTn0nHDIGVmO6wo4rJxbmOOPRQ4l/oAxGRnYU8B8v6w== } + engines: { node: '>=18' } + peerDependencies: + '@langchain/core': '>=0.3.58 <0.4.0' + + '@langchain/textsplitters@0.1.0': + resolution: + { integrity: sha512-djI4uw9rlkAb5iMhtLED+xJebDdAG935AdP4eRTB02R7OB/act55Bj9wsskhZsvuyQRpO4O1wQOp85s6T6GWmw== } + engines: { node: '>=18' } + peerDependencies: + '@langchain/core': '>=0.2.21 <0.4.0' + + '@lit-labs/ssr-dom-shim@1.3.0': + resolution: + { integrity: sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ== } + + '@lit/reactive-element@2.1.0': + resolution: + { integrity: sha512-L2qyoZSQClcBmq0qajBVbhYEcG6iK0XfLn66ifLe/RfC0/ihpc+pl0Wdn8bJ8o+hj38cG0fGXRgSS20MuXn7qA== } + + '@lobehub/emojilib@1.0.0': + resolution: + { integrity: sha512-s9KnjaPjsEefaNv150G3aifvB+J3P4eEKG+epY9zDPS2BeB6+V2jELWqAZll+nkogMaVovjEE813z3V751QwGw== } + + '@lobehub/fluent-emoji@2.0.0': + resolution: + { integrity: sha512-bKjU3sf0+7NppvcdqD/raWvKGJIw8HDJVporNQ7oR8pIPoLeb9IUu/vqIYClOlwfu9qntji7FFySfbdNqXSiJw== } + peerDependencies: + antd: ^5.23.0 + react: ^19.0.0 + react-dom: ^19.0.0 + + '@lobehub/icons@1.98.0': + resolution: + { integrity: sha512-2+t3wX1PdHQnWgHN5ZNrK+X2GBLQkrDGsrvJgks50JyvKYQ8j10yY+qg+epB9uAt2zXzGiAy4A3tNMe7bLoOPQ== } + deprecated: deprecate + peerDependencies: + antd: ^5.23.0 + react: ^19.0.0 + react-dom: ^19.0.0 + + '@lobehub/icons@2.4.0': + resolution: + { integrity: sha512-/qzGIu1lIQftP3vZCrlbF2D7rQEPlSISGdmDFJ6yQquzE8W8RNX5sy+4I1jhqC2uZ+b5YDFYg8evyWrLxUJuNA== } + peerDependencies: + antd: ^5.23.0 + react: ^19.0.0 + react-dom: ^19.0.0 + + '@lobehub/ui@2.4.0': + resolution: + { integrity: sha512-x6VYO4LOKPouIpJ1vvcNj1JrXJ+ov11i+dTceX44b4kQ6EcxafJQSatrghROgnqHvu9wFH+AGrPXyYtSMPJ4tw== } + peerDependencies: + antd: ^5.25.0 + framer-motion: ^12.0.0 + react: ^19.0.0 + react-dom: ^19.0.0 + + '@malept/cross-spawn-promise@1.1.1': + resolution: + { integrity: sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ== } + engines: { node: '>= 10' } + + '@malept/flatpak-bundler@0.4.0': + resolution: + { integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q== } + engines: { node: '>= 10.0.0' } + + '@mapbox/node-pre-gyp@1.0.11': + resolution: + { integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== } + hasBin: true + + '@mdx-js/mdx@3.1.0': + resolution: + { integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw== } + + '@mdx-js/react@3.1.0': + resolution: + { integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ== } + peerDependencies: + '@types/react': '>=16' + react: '>=16' + + '@mermaid-js/parser@0.4.0': + resolution: + { integrity: sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA== } + + '@mixmark-io/domino@2.2.0': + resolution: + { integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw== } + + '@mui/base@5.0.0-beta.40-0': + resolution: + { integrity: sha512-hG3atoDUxlvEy+0mqdMpWd04wca8HKr2IHjW/fAjlkCHQolSLazhZM46vnHjOf15M4ESu25mV/3PgjczyjVM4w== } + engines: { node: '>=12.0.0' } + deprecated: This package has been replaced by @base-ui-components/react + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/core-downloads-tracker@5.17.1': + resolution: + { integrity: sha512-OcZj+cs6EfUD39IoPBOgN61zf1XFVY+imsGoBDwXeSq2UHJZE3N59zzBOVjclck91Ne3e9gudONOeILvHCIhUA== } + + '@mui/icons-material@5.16.14': + resolution: + { integrity: sha512-heL4S+EawrP61xMXBm59QH6HODsu0gxtZi5JtnXF2r+rghzyU/3Uftlt1ij8rmJh+cFdKTQug1L9KkZB5JgpMQ== } + engines: { node: '>=12.0.0' } + peerDependencies: + '@mui/material': ^5.0.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/lab@5.0.0-alpha.175': + resolution: + { integrity: sha512-AvM0Nvnnj7vHc9+pkkQkoE1i+dEbr6gsMdnSfy7X4w3Ljgcj1yrjZhIt3jGTCLzyKVLa6uve5eLluOcGkvMqUA== } + engines: { node: '>=12.0.0' } + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material': '>=5.15.0' + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/material@5.16.14': + resolution: + { integrity: sha512-eSXQVCMKU2xc7EcTxe/X/rC9QsV2jUe8eLM3MUCPYbo6V52eCE436akRIvELq/AqZpxx2bwkq7HC0cRhLB+yaw== } + engines: { node: '>=12.0.0' } + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/private-theming@5.17.1': + resolution: + { integrity: sha512-XMxU0NTYcKqdsG8LRmSoxERPXwMbp16sIXPcLVgLGII/bVNagX0xaheWAwFv8+zDK7tI3ajllkuD3GZZE++ICQ== } + engines: { node: '>=12.0.0' } + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/styled-engine@5.16.14': + resolution: + { integrity: sha512-UAiMPZABZ7p8mUW4akDV6O7N3+4DatStpXMZwPlt+H/dA0lt67qawN021MNND+4QTpjaiMYxbhKZeQcyWCbuKw== } + engines: { node: '>=12.0.0' } + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/system@5.17.1': + resolution: + { integrity: sha512-aJrmGfQpyF0U4D4xYwA6ueVtQcEMebET43CUmKMP7e7iFh3sMIF3sBR0l8Urb4pqx1CBjHAaWgB0ojpND4Q3Jg== } + engines: { node: '>=12.0.0' } + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.2.24': + resolution: + { integrity: sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw== } + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/types@7.4.3': + resolution: + { integrity: sha512-2UCEiK29vtiZTeLdS2d4GndBKacVyxGvReznGXGr+CzW/YhjIX+OHUdCIczZjzcRAgKBGmE9zCIgoV9FleuyRQ== } + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@5.17.1': + resolution: + { integrity: sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg== } + engines: { node: '>=12.0.0' } + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@next/env@14.2.29': + resolution: + { integrity: sha512-UzgLR2eBfhKIQt0aJ7PWH7XRPYw7SXz0Fpzdl5THjUnvxy4kfBk9OU4RNPNiETewEEtaBcExNFNn1QWH8wQTjg== } + + '@next/swc-darwin-arm64@14.2.29': + resolution: + { integrity: sha512-wWtrAaxCVMejxPHFb1SK/PVV1WDIrXGs9ki0C/kUM8ubKHQm+3hU9MouUywCw8Wbhj3pewfHT2wjunLEr/TaLA== } + engines: { node: '>= 10' } + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@14.2.29': + resolution: + { integrity: sha512-7Z/jk+6EVBj4pNLw/JQrvZVrAh9Bv8q81zCFSfvTMZ51WySyEHWVpwCEaJY910LyBftv2F37kuDPQm0w9CEXyg== } + engines: { node: '>= 10' } + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@14.2.29': + resolution: + { integrity: sha512-o6hrz5xRBwi+G7JFTHc+RUsXo2lVXEfwh4/qsuWBMQq6aut+0w98WEnoNwAwt7hkEqegzvazf81dNiwo7KjITw== } + engines: { node: '>= 10' } + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@14.2.29': + resolution: + { integrity: sha512-9i+JEHBOVgqxQ92HHRFlSW1EQXqa/89IVjtHgOqsShCcB/ZBjTtkWGi+SGCJaYyWkr/lzu51NTMCfKuBf7ULNw== } + engines: { node: '>= 10' } + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@14.2.29': + resolution: + { integrity: sha512-B7JtMbkUwHijrGBOhgSQu2ncbCYq9E7PZ7MX58kxheiEOwdkM+jGx0cBb+rN5AeqF96JypEppK6i/bEL9T13lA== } + engines: { node: '>= 10' } + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@14.2.29': + resolution: + { integrity: sha512-yCcZo1OrO3aQ38B5zctqKU1Z3klOohIxug6qdiKO3Q3qNye/1n6XIs01YJ+Uf+TdpZQ0fNrOQI2HrTLF3Zprnw== } + engines: { node: '>= 10' } + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@14.2.29': + resolution: + { integrity: sha512-WnrfeOEtTVidI9Z6jDLy+gxrpDcEJtZva54LYC0bSKQqmyuHzl0ego+v0F/v2aXq0am67BRqo/ybmmt45Tzo4A== } + engines: { node: '>= 10' } + cpu: [arm64] + os: [win32] + + '@next/swc-win32-ia32-msvc@14.2.29': + resolution: + { integrity: sha512-vkcriFROT4wsTdSeIzbxaZjTNTFKjSYmLd8q/GVH3Dn8JmYjUKOuKXHK8n+lovW/kdcpIvydO5GtN+It2CvKWA== } + engines: { node: '>= 10' } + cpu: [ia32] + os: [win32] + + '@next/swc-win32-x64-msvc@14.2.29': + resolution: + { integrity: sha512-iPPwUEKnVs7pwR0EBLJlwxLD7TTHWS/AoVZx1l9ZQzfQciqaFEr5AlYzA2uB6Fyby1IF18t4PL0nTpB+k4Tzlw== } + engines: { node: '>= 10' } + cpu: [x64] + os: [win32] + + '@noble/hashes@1.8.0': + resolution: + { integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== } + engines: { node: ^14.21.3 || >=16 } + + '@nodelib/fs.scandir@2.1.5': + resolution: + { integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== } + engines: { node: '>= 8' } + + '@nodelib/fs.stat@2.0.5': + resolution: + { integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== } + engines: { node: '>= 8' } + + '@nodelib/fs.walk@1.2.8': + resolution: + { integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== } + engines: { node: '>= 8' } + + '@opendocsg/pdf2md@0.2.1': + resolution: + { integrity: sha512-k/yvfrTb+GPTIIm/bMm5IsenTqAFl+IqvkBgFwFlmflS5TT7FOfyRLp8MypVWLAG4G9AnT7AZFbdQYgN/CR5BA== } + hasBin: true + + '@openrouter/ai-sdk-provider@0.4.6': + resolution: + { integrity: sha512-oUa8xtssyUhiKEU/aW662lsZ0HUvIUTRk8vVIF3Ha3KI/DnqX54zmVIuzYnaDpermqhy18CHqblAY4dDt1JW3g== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.0.0 + + '@opentelemetry/api@1.9.0': + resolution: + { integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== } + engines: { node: '>=8.0.0' } + + '@paralleldrive/cuid2@2.2.2': + resolution: + { integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA== } + + '@pkgjs/parseargs@0.11.0': + resolution: + { integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== } + engines: { node: '>=14' } + + '@popperjs/core@2.11.8': + resolution: + { integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== } + + '@prisma/client@6.9.0': + resolution: + { integrity: sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ== } + engines: { node: '>=18.18' } + peerDependencies: + prisma: '*' + typescript: '>=5.1.0' + peerDependenciesMeta: + prisma: + optional: true + typescript: + optional: true + + '@prisma/config@6.9.0': + resolution: + { integrity: sha512-Wcfk8/lN3WRJd5w4jmNQkUwhUw0eksaU/+BlAJwPQKW10k0h0LC9PD/6TQFmqKVbHQL0vG2z266r0S1MPzzhbA== } + + '@prisma/debug@6.9.0': + resolution: + { integrity: sha512-bFeur/qi/Q+Mqk4JdQ3R38upSYPebv5aOyD1RKywVD+rAMLtRkmTFn28ZuTtVOnZHEdtxnNOCH+bPIeSGz1+Fg== } + + '@prisma/engines-version@6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e': + resolution: + { integrity: sha512-Qp9gMoBHgqhKlrvumZWujmuD7q4DV/gooEyPCLtbkc13EZdSz2RsGUJ5mHb3RJgAbk+dm6XenqG7obJEhXcJ6Q== } + + '@prisma/engines@6.9.0': + resolution: + { integrity: sha512-im0X0bwDLA0244CDf8fuvnLuCQcBBdAGgr+ByvGfQY9wWl6EA+kRGwVk8ZIpG65rnlOwtaWIr/ZcEU5pNVvq9g== } + + '@prisma/fetch-engine@6.9.0': + resolution: + { integrity: sha512-PMKhJdl4fOdeE3J3NkcWZ+tf3W6rx3ht/rLU8w4SXFRcLhd5+3VcqY4Kslpdm8osca4ej3gTfB3+cSk5pGxgFg== } + + '@prisma/get-platform@6.9.0': + resolution: + { integrity: sha512-/B4n+5V1LI/1JQcHp+sUpyRT1bBgZVPHbsC4lt4/19Xp4jvNIVcq5KYNtQDk5e/ukTSjo9PZVAxxy9ieFtlpTQ== } + + '@radix-ui/primitive@1.0.0': + resolution: + { integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA== } + + '@radix-ui/react-arrow@1.0.2': + resolution: + { integrity: sha512-fqYwhhI9IarZ0ll2cUSfKuXHlJK0qE4AfnRrPBbRwEH/4mGQn04/QFGomLi8TXWIdv9WJk//KgGm+aDxVIr1wA== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-compose-refs@1.0.0': + resolution: + { integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-compose-refs@1.1.2': + resolution: + { integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg== } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.0.0': + resolution: + { integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-dismissable-layer@1.0.3': + resolution: + { integrity: sha512-nXZOvFjOuHS1ovumntGV7NNoLaEp9JEvTht3MBjP44NSW5hUKj/8OnfN3+8WmB+CEhN44XaGhpHoSsUIEl5P7Q== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-id@1.0.0': + resolution: + { integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-popper@1.1.1': + resolution: + { integrity: sha512-keYDcdMPNMjSC8zTsZ8wezUMiWM9Yj14wtF3s0PTIs9srnEPC9Kt2Gny1T3T81mmSeyDjZxsD9N5WCwNNb712w== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-portal@1.0.2': + resolution: + { integrity: sha512-swu32idoCW7KA2VEiUZGBSu9nB6qwGdV6k6HYhUoOo3M1FFpD+VgLzUqtt3mwL1ssz7r2x8MggpLSQach2Xy/Q== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-presence@1.0.0': + resolution: + { integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-primitive@1.0.2': + resolution: + { integrity: sha512-zY6G5Qq4R8diFPNwtyoLRZBxzu1Z+SXMlfYpChN7Dv8gvmx9X3qhDqiLWvKseKVJMuedFeU/Sa0Sy/Ia+t06Dw== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-slot@1.0.1': + resolution: + { integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-slot@1.2.3': + resolution: + { integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A== } + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tooltip@1.0.5': + resolution: + { integrity: sha512-cDKVcfzyO6PpckZekODJZDe5ZxZ2fCZlzKzTmPhe4mX9qTHRfLcKgqb0OKf22xLwDequ2tVleim+ZYx3rabD5w== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-use-callback-ref@1.0.0': + resolution: + { integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-use-controllable-state@1.0.0': + resolution: + { integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-use-escape-keydown@1.0.2': + resolution: + { integrity: sha512-DXGim3x74WgUv+iMNCF+cAo8xUHHeqvjx8zs7trKf+FkQKPQXLk2sX7Gx1ysH7Q76xCpZuxIJE7HLPxRE+Q+GA== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-use-layout-effect@1.0.0': + resolution: + { integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-use-rect@1.0.0': + resolution: + { integrity: sha512-TB7pID8NRMEHxb/qQJpvSt3hQU4sqNPM1VCTjTRjEOa7cEop/QMuq8S6fb/5Tsz64kqSvB9WnwsDHtjnrM9qew== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-use-size@1.0.0': + resolution: + { integrity: sha512-imZ3aYcoYCKhhgNpkNDh/aTiU05qw9hX+HHI1QDBTyIlcFjgeFlKKySNGMwTp7nYFLQg/j0VA2FmCY4WPDDHMg== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/react-visually-hidden@1.0.2': + resolution: + { integrity: sha512-qirnJxtYn73HEk1rXL12/mXnu2rwsNHDID10th2JGtdK25T9wX+mxRmGt7iPSahw512GbZOc0syZX1nLQGoEOg== } + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + + '@radix-ui/rect@1.0.0': + resolution: + { integrity: sha512-d0O68AYy/9oeEy1DdC07bz1/ZXX+DqCskRd3i4JzLSTXwefzaepQrKjXC7aNM8lTHjFLDO0pDgaEiQ7jEk+HVg== } + + '@rc-component/async-validator@5.0.4': + resolution: + { integrity: sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg== } + engines: { node: '>=14.x' } + + '@rc-component/color-picker@2.0.1': + resolution: + { integrity: sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/context@1.4.0': + resolution: + { integrity: sha512-kFcNxg9oLRMoL3qki0OMxK+7g5mypjgaaJp/pkOis/6rVxma9nJBF/8kCIuTYHUQNr0ii7MxqE33wirPZLJQ2w== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/mini-decimal@1.1.0': + resolution: + { integrity: sha512-jS4E7T9Li2GuYwI6PyiVXmxTiM6b07rlD9Ge8uGZSCz3WlzcG5ZK7g5bbuKNeZ9pgUuPK/5guV781ujdVpm4HQ== } + engines: { node: '>=8.x' } + + '@rc-component/mutate-observer@1.1.0': + resolution: + { integrity: sha512-QjrOsDXQusNwGZPf4/qRQasg7UFEj06XiCJ8iuiq/Io7CrHrgVi6Uuetw60WAMG1799v+aM8kyc+1L/GBbHSlw== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/portal@1.1.2': + resolution: + { integrity: sha512-6f813C0IsasTZms08kfA8kPAGxbbkYToa8ALaiDIGGECU4i9hj8Plgbx0sNJDrey3EtHO30hmdaxtT0138xZcg== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/qrcode@1.0.0': + resolution: + { integrity: sha512-L+rZ4HXP2sJ1gHMGHjsg9jlYBX/SLN2D6OxP9Zn3qgtpMWtO2vUfxVFwiogHpAIqs54FnALxraUy/BCO1yRIgg== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/tour@1.15.1': + resolution: + { integrity: sha512-Tr2t7J1DKZUpfJuDZWHxyxWpfmj8EZrqSgyMZ+BCdvKZ6r1UDsfU46M/iWAAFBy961Ssfom2kv5f3UcjIL2CmQ== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@rc-component/trigger@2.2.6': + resolution: + { integrity: sha512-/9zuTnWwhQ3S3WT1T8BubuFTT46kvnXgaERR9f4BTKyn61/wpf/BvbImzYBubzJibU707FxwbKszLlHjcLiv1Q== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + '@reduxjs/toolkit@2.11.2': + resolution: + { integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ== } + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@shikijs/core@3.6.0': + resolution: + { integrity: sha512-9By7Xb3olEX0o6UeJyPLI1PE1scC4d3wcVepvtv2xbuN9/IThYN4Wcwh24rcFeASzPam11MCq8yQpwwzCgSBRw== } + + '@shikijs/engine-javascript@3.6.0': + resolution: + { integrity: sha512-7YnLhZG/TU05IHMG14QaLvTW/9WiK8SEYafceccHUSXs2Qr5vJibUwsDfXDLmRi0zHdzsxrGKpSX6hnqe0k8nA== } + + '@shikijs/engine-oniguruma@3.6.0': + resolution: + { integrity: sha512-nmOhIZ9yT3Grd+2plmW/d8+vZ2pcQmo/UnVwXMUXAKTXdi+LK0S08Ancrz5tQQPkxvjBalpMW2aKvwXfelauvA== } + + '@shikijs/langs@3.6.0': + resolution: + { integrity: sha512-IdZkQJaLBu1LCYCwkr30hNuSDfllOT8RWYVZK1tD2J03DkiagYKRxj/pDSl8Didml3xxuyzUjgtioInwEQM/TA== } + + '@shikijs/themes@3.6.0': + resolution: + { integrity: sha512-Fq2j4nWr1DF4drvmhqKq8x5vVQ27VncF8XZMBuHuQMZvUSS3NBgpqfwz/FoGe36+W6PvniZ1yDlg2d4kmYDU6w== } + + '@shikijs/transformers@3.6.0': + resolution: + { integrity: sha512-PYkU54lYV0RCaUG8n2FNTF+YWiU3uPhcjLGq2x/C8lIrUX9GVnRb3bK+R5xtdFHbuctntATKm7ondp/H/dux9Q== } + + '@shikijs/types@3.6.0': + resolution: + { integrity: sha512-cLWFiToxYu0aAzJqhXTQsFiJRTFDAGl93IrMSBNaGSzs7ixkLfdG6pH11HipuWFGW5vyx4X47W8HDQ7eSrmBUg== } + + '@shikijs/vscode-textmate@10.0.2': + resolution: + { integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg== } + + '@sideway/address@4.1.5': + resolution: + { integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q== } + + '@sideway/formula@3.0.1': + resolution: + { integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg== } + + '@sideway/pinpoint@2.0.0': + resolution: + { integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== } + + '@sindresorhus/is@4.6.0': + resolution: + { integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== } + engines: { node: '>=10' } + + '@splinetool/runtime@0.9.526': + resolution: + { integrity: sha512-qznHbXA5aKwDbCgESAothCNm1IeEZcmNWG145p5aXj4w5uoqR1TZ9qkTHTKLTsUbHeitCwdhzmRqan1kxboLgQ== } + + '@standard-schema/spec@1.0.0': + resolution: + { integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA== } + + '@standard-schema/utils@0.3.0': + resolution: + { integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== } + + '@stitches/react@1.2.8': + resolution: + { integrity: sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA== } + peerDependencies: + react: '>= 16.3.0' + + '@swc/counter@0.1.3': + resolution: + { integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== } + + '@swc/helpers@0.5.5': + resolution: + { integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A== } + + '@szmarczak/http-timer@4.0.6': + resolution: + { integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== } + engines: { node: '>=10' } + + '@tootallnate/once@2.0.0': + resolution: + { integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== } + engines: { node: '>= 10' } + + '@types/cacheable-request@6.0.3': + resolution: + { integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw== } + + '@types/conventional-commits-parser@5.0.1': + resolution: + { integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ== } + + '@types/d3-array@3.2.1': + resolution: + { integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg== } + + '@types/d3-axis@3.0.6': + resolution: + { integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw== } + + '@types/d3-brush@3.0.6': + resolution: + { integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A== } + + '@types/d3-chord@3.0.6': + resolution: + { integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg== } + + '@types/d3-color@3.1.3': + resolution: + { integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A== } + + '@types/d3-contour@3.0.6': + resolution: + { integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg== } + + '@types/d3-delaunay@6.0.4': + resolution: + { integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw== } + + '@types/d3-dispatch@3.0.6': + resolution: + { integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ== } + + '@types/d3-drag@3.0.7': + resolution: + { integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ== } + + '@types/d3-dsv@3.0.7': + resolution: + { integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g== } + + '@types/d3-ease@3.0.2': + resolution: + { integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA== } + + '@types/d3-fetch@3.0.7': + resolution: + { integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA== } + + '@types/d3-force@3.0.10': + resolution: + { integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw== } + + '@types/d3-format@3.0.4': + resolution: + { integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g== } + + '@types/d3-geo@3.1.0': + resolution: + { integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ== } + + '@types/d3-hierarchy@3.1.7': + resolution: + { integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg== } + + '@types/d3-interpolate@3.0.4': + resolution: + { integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA== } + + '@types/d3-path@3.1.1': + resolution: + { integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg== } + + '@types/d3-polygon@3.0.2': + resolution: + { integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA== } + + '@types/d3-quadtree@3.0.6': + resolution: + { integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg== } + + '@types/d3-random@3.0.3': + resolution: + { integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ== } + + '@types/d3-scale-chromatic@3.1.0': + resolution: + { integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ== } + + '@types/d3-scale@4.0.9': + resolution: + { integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw== } + + '@types/d3-selection@3.0.11': + resolution: + { integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w== } + + '@types/d3-shape@3.1.7': + resolution: + { integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg== } + + '@types/d3-time-format@4.0.3': + resolution: + { integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg== } + + '@types/d3-time@3.0.4': + resolution: + { integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g== } + + '@types/d3-timer@3.0.2': + resolution: + { integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== } + + '@types/d3-transition@3.0.9': + resolution: + { integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg== } + + '@types/d3-zoom@3.0.8': + resolution: + { integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw== } + + '@types/d3@7.4.3': + resolution: + { integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww== } + + '@types/debug@4.1.12': + resolution: + { integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== } + + '@types/diff-match-patch@1.0.36': + resolution: + { integrity: sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg== } + + '@types/estree-jsx@1.0.5': + resolution: + { integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== } + + '@types/estree@1.0.8': + resolution: + { integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== } + + '@types/fs-extra@9.0.13': + resolution: + { integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA== } + + '@types/geojson@7946.0.16': + resolution: + { integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg== } + + '@types/hast@3.0.4': + resolution: + { integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== } + + '@types/http-cache-semantics@4.0.4': + resolution: + { integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA== } + + '@types/katex@0.16.7': + resolution: + { integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ== } + + '@types/keyv@3.1.4': + resolution: + { integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== } + + '@types/mdast@4.0.4': + resolution: + { integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== } + + '@types/mdx@2.0.13': + resolution: + { integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw== } + + '@types/ms@2.1.0': + resolution: + { integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== } + + '@types/node-fetch@2.6.12': + resolution: + { integrity: sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA== } + + '@types/node@18.19.111': + resolution: + { integrity: sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw== } + + '@types/node@22.15.31': + resolution: + { integrity: sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw== } + + '@types/node@24.0.1': + resolution: + { integrity: sha512-MX4Zioh39chHlDJbKmEgydJDS3tspMP/lnQC67G3SWsTnb9NeYVWOjkxpOSy4oMfPs4StcWHwBrvUb4ybfnuaw== } + + '@types/parse-json@4.0.2': + resolution: + { integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== } + + '@types/plist@3.0.5': + resolution: + { integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA== } + + '@types/prop-types@15.7.15': + resolution: + { integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw== } + + '@types/react-transition-group@4.4.12': + resolution: + { integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w== } + peerDependencies: + '@types/react': '*' + + '@types/react@19.1.8': + resolution: + { integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== } + + '@types/responselike@1.0.3': + resolution: + { integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw== } + + '@types/retry@0.12.0': + resolution: + { integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== } + + '@types/trusted-types@2.0.7': + resolution: + { integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== } + + '@types/unist@2.0.11': + resolution: + { integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== } + + '@types/unist@3.0.3': + resolution: + { integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== } + + '@types/use-sync-external-store@0.0.6': + resolution: + { integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg== } + + '@types/uuid@10.0.0': + resolution: + { integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ== } + + '@types/verror@1.10.11': + resolution: + { integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg== } + + '@types/yauzl@2.10.3': + resolution: + { integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== } + + '@ungap/structured-clone@1.3.0': + resolution: + { integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== } + + '@use-gesture/core@10.3.1': + resolution: + { integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw== } + + '@use-gesture/react@10.3.1': + resolution: + { integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g== } + peerDependencies: + react: '>= 16.8.0' + + '@xmldom/xmldom@0.8.10': + resolution: + { integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw== } + engines: { node: '>=10.0.0' } + + JSONStream@1.3.5: + resolution: + { integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== } + hasBin: true + + abbrev@1.1.1: + resolution: + { integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== } + + abort-controller@3.0.0: + resolution: + { integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== } + engines: { node: '>=6.5' } + + acorn-jsx@5.3.2: + resolution: + { integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== } + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: + { integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== } + engines: { node: '>=0.4.0' } + hasBin: true + + adler-32@1.3.1: + resolution: + { integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A== } + engines: { node: '>=0.8' } + + adm-zip@0.5.16: + resolution: + { integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ== } + engines: { node: '>=12.0' } + + agent-base@6.0.2: + resolution: + { integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== } + engines: { node: '>= 6.0.0' } + + agentkeepalive@4.6.0: + resolution: + { integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ== } + engines: { node: '>= 8.0.0' } + + ahooks@3.8.5: + resolution: + { integrity: sha512-Y+MLoJpBXVdjsnnBjE5rOSPkQ4DK+8i5aPDzLJdIOsCpo/fiAeXcBY1Y7oWgtOK0TpOz0gFa/XcyO1UGdoqLcw== } + engines: { node: '>=8.0.0' } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + ai@4.3.16: + resolution: + { integrity: sha512-KUDwlThJ5tr2Vw0A1ZkbDKNME3wzWhuVfAOwIvFUzl1TPVDFAXDFTXio3p+jaKneB+dKNCvFFlolYmmgHttG1g== } + engines: { node: '>=18' } + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + zod: ^3.23.8 + peerDependenciesMeta: + react: + optional: true + + ajv-keywords@3.5.2: + resolution: + { integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== } + peerDependencies: + ajv: ^6.9.1 + + ajv@6.12.6: + resolution: + { integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== } + + ajv@8.17.1: + resolution: + { integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== } + + ansi-escapes@7.0.0: + resolution: + { integrity: sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw== } + engines: { node: '>=18' } + + ansi-regex@2.1.1: + resolution: + { integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== } + engines: { node: '>=0.10.0' } + + ansi-regex@5.0.1: + resolution: + { integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== } + engines: { node: '>=8' } + + ansi-regex@6.1.0: + resolution: + { integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== } + engines: { node: '>=12' } + + ansi-styles@2.2.1: + resolution: + { integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA== } + engines: { node: '>=0.10.0' } + + ansi-styles@4.3.0: + resolution: + { integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== } + engines: { node: '>=8' } + + ansi-styles@5.2.0: + resolution: + { integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== } + engines: { node: '>=10' } + + ansi-styles@6.2.1: + resolution: + { integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== } + engines: { node: '>=12' } + + antd-style@3.7.1: + resolution: + { integrity: sha512-CQOfddVp4aOvBfCepa+Kj2e7ap+2XBINg1Kn2osdE3oQvrD7KJu/K0sfnLcFLkgCJygbxmuazYdWLKb+drPDYA== } + peerDependencies: + antd: '>=5.8.1' + react: '>=18' + + antd@5.26.0: + resolution: + { integrity: sha512-iMPYKFTo2HvIRGutUOuN5AG+Uf+B2QaqcGQbdPp/100fqV3FAil6vFZLVuV3C4XEUOlDNkkUlJKhLR9V5rzIEg== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + app-builder-bin@4.0.0: + resolution: + { integrity: sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA== } + + app-builder-lib@24.13.3: + resolution: + { integrity: sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig== } + engines: { node: '>=14.0.0' } + peerDependencies: + dmg-builder: 24.13.3 + electron-builder-squirrel-windows: 24.13.3 + + aproba@2.0.0: + resolution: + { integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== } + + archiver-utils@2.1.0: + resolution: + { integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== } + engines: { node: '>= 6' } + + archiver-utils@3.0.4: + resolution: + { integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== } + engines: { node: '>= 10' } + + archiver@5.3.2: + resolution: + { integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== } + engines: { node: '>= 10' } + + are-we-there-yet@2.0.0: + resolution: + { integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== } + engines: { node: '>=10' } + deprecated: This package is no longer supported. + + argparse@1.0.10: + resolution: + { integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== } + + argparse@2.0.1: + resolution: + { integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== } + + array-ify@1.0.0: + resolution: + { integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng== } + + array-union@2.1.0: + resolution: + { integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== } + engines: { node: '>=8' } + + asap@2.0.6: + resolution: + { integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== } + + asn1@0.2.6: + resolution: + { integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== } + + assert-plus@0.2.0: + resolution: + { integrity: sha512-u1L0ZLywRziOVjUhRxI0Qg9G+4RnFB9H/Rq40YWn0dieDgO7vAYeJz6jKAO6t/aruzlDFLAPkQTT87e+f8Imaw== } + engines: { node: '>=0.8' } + + assert-plus@1.0.0: + resolution: + { integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== } + engines: { node: '>=0.8' } + + assign-symbols@1.0.0: + resolution: + { integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== } + engines: { node: '>=0.10.0' } + + astral-regex@2.0.0: + resolution: + { integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== } + engines: { node: '>=8' } + + astring@1.9.0: + resolution: + { integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg== } + hasBin: true + + async-exit-hook@2.0.1: + resolution: + { integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw== } + engines: { node: '>=0.12.0' } + + async@2.6.4: + resolution: + { integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA== } + + async@3.2.6: + resolution: + { integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== } + + asynckit@0.4.0: + resolution: + { integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== } + + at-least-node@1.0.0: + resolution: + { integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== } + engines: { node: '>= 4.0.0' } + + attr-accept@2.2.5: + resolution: + { integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ== } + engines: { node: '>=4' } + + aws-sign2@0.6.0: + resolution: + { integrity: sha512-JnJpAS0p9RmixkOvW2XwDxxzs1bd4/VAGIl6Q0EC5YOo+p+hqIhtDhn/nmFnB/xUNXbLkpE2mOjgVIBRKD4xYw== } + + aws4@1.13.2: + resolution: + { integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== } + + axios@1.9.0: + resolution: + { integrity: sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg== } + + babel-plugin-macros@3.1.0: + resolution: + { integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== } + engines: { node: '>=10', npm: '>=6' } + + babel-plugin-polyfill-corejs2@0.4.13: + resolution: + { integrity: sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g== } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.11.1: + resolution: + { integrity: sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ== } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.4: + resolution: + { integrity: sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw== } + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + bail@2.0.2: + resolution: + { integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== } + + balanced-match@1.0.2: + resolution: + { integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== } + + base64-js@1.5.1: + resolution: + { integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== } + + bcrypt-pbkdf@1.0.2: + resolution: + { integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== } + + bl@1.1.2: + resolution: + { integrity: sha512-uVVYHEQk+OuWvCi5U+iquVXvvGCWXKawjwELIR2XMLsqfV/e2sGDClVBs8OlGIgGsStPRY/Es311YKYIlYCWAg== } + + bl@4.1.0: + resolution: + { integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== } + + bluebird-lst@1.0.9: + resolution: + { integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw== } + + bluebird@3.4.7: + resolution: + { integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== } + + bluebird@3.7.2: + resolution: + { integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== } + + boolean@3.2.0: + resolution: + { integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw== } + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + + boom@2.10.1: + resolution: + { integrity: sha512-KbiZEa9/vofNcVJXGwdWWn25reQ3V3dHBWbS07FTF3/TOehLnm9GEhJV4T6ZvGPkShRpmUqYwnaCrkj0mRnP6Q== } + engines: { node: '>=0.10.40' } + deprecated: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). + + brace-expansion@1.1.12: + resolution: + { integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== } + + brace-expansion@2.0.2: + resolution: + { integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== } + + braces@3.0.3: + resolution: + { integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== } + engines: { node: '>=8' } + + browserslist@4.25.0: + resolution: + { integrity: sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA== } + engines: { node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7 } + hasBin: true + + buffer-crc32@0.2.13: + resolution: + { integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== } + + buffer-equal@1.0.1: + resolution: + { integrity: sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg== } + engines: { node: '>=0.4' } + + buffer-from@1.1.2: + resolution: + { integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== } + + buffer@5.7.1: + resolution: + { integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== } + + builder-util-runtime@9.2.4: + resolution: + { integrity: sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA== } + engines: { node: '>=12.0.0' } + + builder-util-runtime@9.3.1: + resolution: + { integrity: sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ== } + engines: { node: '>=12.0.0' } + + builder-util@24.13.1: + resolution: + { integrity: sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA== } + + busboy@1.6.0: + resolution: + { integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== } + engines: { node: '>=10.16.0' } + + cacheable-lookup@5.0.4: + resolution: + { integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== } + engines: { node: '>=10.6.0' } + + cacheable-request@7.0.4: + resolution: + { integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg== } + engines: { node: '>=8' } + + call-bind-apply-helpers@1.0.2: + resolution: + { integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== } + engines: { node: '>= 0.4' } + + callsites@3.1.0: + resolution: + { integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== } + engines: { node: '>=6' } + + camelcase@6.3.0: + resolution: + { integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== } + engines: { node: '>=10' } + + caniuse-lite@1.0.30001722: + resolution: + { integrity: sha512-DCQHBBZtiK6JVkAGw7drvAMK0Q0POD/xZvEmDp6baiMMP6QXXk9HpD6mNYBZWhOPG6LvIDb82ITqtWjhDckHCA== } + + canvas@2.11.2: + resolution: + { integrity: sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw== } + engines: { node: '>=6' } + + caseless@0.11.0: + resolution: + { integrity: sha512-ODLXH644w9C2fMPAm7bMDQ3GRvipZWZfKc+8As6hIadRIelE0n0xZuN38NS6kiK3KPEVrpymmQD8bvncAHWQkQ== } + + ccount@2.0.1: + resolution: + { integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== } + + cfb@1.2.2: + resolution: + { integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA== } + engines: { node: '>=0.8' } + + chalk@1.1.3: + resolution: + { integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A== } + engines: { node: '>=0.10.0' } + + chalk@4.1.2: + resolution: + { integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== } + engines: { node: '>=10' } + + chalk@5.4.1: + resolution: + { integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w== } + engines: { node: ^12.17.0 || ^14.13 || >=16.0.0 } + + character-entities-html4@2.1.0: + resolution: + { integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== } + + character-entities-legacy@3.0.0: + resolution: + { integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== } + + character-entities@2.0.2: + resolution: + { integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== } + + character-reference-invalid@2.0.1: + resolution: + { integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== } + + chevrotain-allstar@0.3.1: + resolution: + { integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw== } + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: + { integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw== } + + chownr@1.1.4: + resolution: + { integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== } + + chownr@2.0.0: + resolution: + { integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== } + engines: { node: '>=10' } + + chroma-js@3.1.2: + resolution: + { integrity: sha512-IJnETTalXbsLx1eKEgx19d5L6SRM7cH4vINw/99p/M11HCuXGRWL+6YmCm7FWFGIo6dtWuQoQi1dc5yQ7ESIHg== } + + chromium-pickle-js@0.2.0: + resolution: + { integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw== } + + ci-info@3.9.0: + resolution: + { integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== } + engines: { node: '>=8' } + + class-variance-authority@0.7.1: + resolution: + { integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg== } + + classnames@2.5.1: + resolution: + { integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== } + + cli-cursor@5.0.0: + resolution: + { integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw== } + engines: { node: '>=18' } + + cli-truncate@2.1.0: + resolution: + { integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== } + engines: { node: '>=8' } + + cli-truncate@4.0.0: + resolution: + { integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA== } + engines: { node: '>=18' } + + client-only@0.0.1: + resolution: + { integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== } + + cliui@7.0.4: + resolution: + { integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== } + + cliui@8.0.1: + resolution: + { integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== } + engines: { node: '>=12' } + + clone-response@1.0.3: + resolution: + { integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== } + + clsx@1.2.1: + resolution: + { integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg== } + engines: { node: '>=6' } + + clsx@2.1.1: + resolution: + { integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== } + engines: { node: '>=6' } + + co-prompt@1.0.0: + resolution: + { integrity: sha512-uKmEbjDnL9SJTb+TNfIFsATe1F3IsNsR7KDGUG1hq7ColkMV0MSn7dg3eKVS+3wwtyvVqrgfIwi39NOJiknO7Q== } + + co@4.6.0: + resolution: + { integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== } + engines: { iojs: '>= 1.0.0', node: '>= 0.12.0' } + + codepage@1.15.0: + resolution: + { integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA== } + engines: { node: '>=0.8' } + + collapse-white-space@2.1.0: + resolution: + { integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw== } + + color-convert@2.0.1: + resolution: + { integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== } + engines: { node: '>=7.0.0' } + + color-name@1.1.4: + resolution: + { integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== } + + color-string@1.9.1: + resolution: + { integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== } + + color-support@1.1.3: + resolution: + { integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== } + hasBin: true + + color@4.2.3: + resolution: + { integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== } + engines: { node: '>=12.5.0' } + + colord@2.9.3: + resolution: + { integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== } + + colorette@2.0.20: + resolution: + { integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== } + + combined-stream@1.0.8: + resolution: + { integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== } + engines: { node: '>= 0.8' } + + comma-separated-tokens@2.0.3: + resolution: + { integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== } + + commander@13.1.0: + resolution: + { integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw== } + engines: { node: '>=18' } + + commander@2.9.0: + resolution: + { integrity: sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A== } + engines: { node: '>= 0.6.x' } + + commander@5.1.0: + resolution: + { integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== } + engines: { node: '>= 6' } + + commander@7.2.0: + resolution: + { integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== } + engines: { node: '>= 10' } + + commander@8.3.0: + resolution: + { integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== } + engines: { node: '>= 12' } + + compare-func@2.0.0: + resolution: + { integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== } + + compare-version@0.1.2: + resolution: + { integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A== } + engines: { node: '>=0.10.0' } + + compress-commons@4.1.2: + resolution: + { integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== } + engines: { node: '>= 10' } + + compute-scroll-into-view@3.1.1: + resolution: + { integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw== } + + concat-map@0.0.1: + resolution: + { integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== } + + concurrently@8.2.2: + resolution: + { integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg== } + engines: { node: ^14.13.0 || >=16.0.0 } + hasBin: true + + confbox@0.1.8: + resolution: + { integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w== } + + confbox@0.2.2: + resolution: + { integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ== } + + config-file-ts@0.2.6: + resolution: + { integrity: sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w== } + + console-control-strings@1.1.0: + resolution: + { integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== } + + console-table-printer@2.14.3: + resolution: + { integrity: sha512-X5OCFnjYlXzRuC8ac5hPA2QflRjJvNKJocMhlnqK/Ap7q3DHXr0NJ0TGzwmEKOiOdJrjsSwEd0m+a32JAYPrKQ== } + + conventional-changelog-angular@7.0.0: + resolution: + { integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ== } + engines: { node: '>=16' } + + conventional-changelog-conventionalcommits@7.0.2: + resolution: + { integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w== } + engines: { node: '>=16' } + + conventional-commits-parser@5.0.0: + resolution: + { integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA== } + engines: { node: '>=16' } + hasBin: true + + convert-source-map@1.9.0: + resolution: + { integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== } + + convert-source-map@2.0.0: + resolution: + { integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== } + + copy-to-clipboard@3.3.3: + resolution: + { integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA== } + + core-js-compat@3.43.0: + resolution: + { integrity: sha512-2GML2ZsCc5LR7hZYz4AXmjQw8zuy2T//2QntwdnpuYI7jteT6GVYJL7F6C2C57R7gSYrcqVW3lAALefdbhBLDA== } + + core-util-is@1.0.2: + resolution: + { integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== } + + core-util-is@1.0.3: + resolution: + { integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== } + + cose-base@1.0.3: + resolution: + { integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg== } + + cose-base@2.2.0: + resolution: + { integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g== } + + cosmiconfig-typescript-loader@6.1.0: + resolution: + { integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g== } + engines: { node: '>=v18' } + peerDependencies: + '@types/node': '*' + cosmiconfig: '>=9' + typescript: '>=5' + + cosmiconfig@7.1.0: + resolution: + { integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== } + engines: { node: '>=10' } + + cosmiconfig@9.0.0: + resolution: + { integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== } + engines: { node: '>=14' } + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + crc-32@1.2.2: + resolution: + { integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== } + engines: { node: '>=0.8' } + hasBin: true + + crc32-stream@4.0.3: + resolution: + { integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== } + engines: { node: '>= 10' } + + crc@3.8.0: + resolution: + { integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ== } + + cross-spawn@7.0.6: + resolution: + { integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== } + engines: { node: '>= 8' } + + cryptiles@2.0.5: + resolution: + { integrity: sha512-FFN5KwpvvQTTS5hWPxrU8/QE4kQUc6uwZcrnlMBN82t1MgAtq8mnoDwINBly9Tdr02seeIIhtdF+UH1feBYGog== } + engines: { node: '>=0.10.40' } + deprecated: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). + + csstype@3.1.3: + resolution: + { integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw== } + + cytoscape-cose-bilkent@4.1.0: + resolution: + { integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ== } + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: + { integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ== } + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.32.0: + resolution: + { integrity: sha512-5JHBC9n75kz5851jeklCPmZWcg3hUe6sjqJvyk3+hVqFaKcHwHgxsjeN1yLmggoUc6STbtm9/NQyabQehfjvWQ== } + engines: { node: '>=0.10' } + + d3-array@2.12.1: + resolution: + { integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ== } + + d3-array@3.2.4: + resolution: + { integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg== } + engines: { node: '>=12' } + + d3-axis@3.0.0: + resolution: + { integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw== } + engines: { node: '>=12' } + + d3-brush@3.0.0: + resolution: + { integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ== } + engines: { node: '>=12' } + + d3-chord@3.0.1: + resolution: + { integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g== } + engines: { node: '>=12' } + + d3-color@3.1.0: + resolution: + { integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA== } + engines: { node: '>=12' } + + d3-contour@4.0.2: + resolution: + { integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA== } + engines: { node: '>=12' } + + d3-delaunay@6.0.4: + resolution: + { integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A== } + engines: { node: '>=12' } + + d3-dispatch@3.0.1: + resolution: + { integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg== } + engines: { node: '>=12' } + + d3-drag@3.0.0: + resolution: + { integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg== } + engines: { node: '>=12' } + + d3-dsv@3.0.1: + resolution: + { integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q== } + engines: { node: '>=12' } + hasBin: true + + d3-ease@3.0.1: + resolution: + { integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w== } + engines: { node: '>=12' } + + d3-fetch@3.0.1: + resolution: + { integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw== } + engines: { node: '>=12' } + + d3-force@3.0.0: + resolution: + { integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg== } + engines: { node: '>=12' } + + d3-format@3.1.0: + resolution: + { integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== } + engines: { node: '>=12' } + + d3-geo@3.1.1: + resolution: + { integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q== } + engines: { node: '>=12' } + + d3-hierarchy@3.1.2: + resolution: + { integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA== } + engines: { node: '>=12' } + + d3-interpolate@3.0.1: + resolution: + { integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g== } + engines: { node: '>=12' } + + d3-path@1.0.9: + resolution: + { integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg== } + + d3-path@3.1.0: + resolution: + { integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ== } + engines: { node: '>=12' } + + d3-polygon@3.0.1: + resolution: + { integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg== } + engines: { node: '>=12' } + + d3-quadtree@3.0.1: + resolution: + { integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw== } + engines: { node: '>=12' } + + d3-random@3.0.1: + resolution: + { integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ== } + engines: { node: '>=12' } + + d3-sankey@0.12.3: + resolution: + { integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ== } + + d3-scale-chromatic@3.1.0: + resolution: + { integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ== } + engines: { node: '>=12' } + + d3-scale@4.0.2: + resolution: + { integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ== } + engines: { node: '>=12' } + + d3-selection@3.0.0: + resolution: + { integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== } + engines: { node: '>=12' } + + d3-shape@1.3.7: + resolution: + { integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw== } + + d3-shape@3.2.0: + resolution: + { integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA== } + engines: { node: '>=12' } + + d3-time-format@4.1.0: + resolution: + { integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg== } + engines: { node: '>=12' } + + d3-time@3.1.0: + resolution: + { integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q== } + engines: { node: '>=12' } + + d3-timer@3.0.1: + resolution: + { integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== } + engines: { node: '>=12' } + + d3-transition@3.0.1: + resolution: + { integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w== } + engines: { node: '>=12' } + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: + { integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw== } + engines: { node: '>=12' } + + d3@7.9.0: + resolution: + { integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA== } + engines: { node: '>=12' } + + dagre-d3-es@7.0.11: + resolution: + { integrity: sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw== } + + dargs@8.1.0: + resolution: + { integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw== } + engines: { node: '>=12' } + + dashdash@1.14.1: + resolution: + { integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== } + engines: { node: '>=0.10' } + + date-fns@2.30.0: + resolution: + { integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== } + engines: { node: '>=0.11' } + + dayjs@1.11.13: + resolution: + { integrity: sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== } + + debug@4.4.1: + resolution: + { integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ== } + engines: { node: '>=6.0' } + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: + { integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== } + engines: { node: '>=0.10.0' } + + decimal.js-light@2.5.1: + resolution: + { integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== } + + decode-named-character-reference@1.1.0: + resolution: + { integrity: sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w== } + + decode-uri-component@0.4.1: + resolution: + { integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ== } + engines: { node: '>=14.16' } + + decompress-response@4.2.1: + resolution: + { integrity: sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw== } + engines: { node: '>=8' } + + decompress-response@6.0.0: + resolution: + { integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== } + engines: { node: '>=10' } + + deep-extend@0.6.0: + resolution: + { integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== } + engines: { node: '>=4.0.0' } + + defer-to-connect@2.0.1: + resolution: + { integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== } + engines: { node: '>=10' } + + define-data-property@1.1.4: + resolution: + { integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== } + engines: { node: '>= 0.4' } + + define-properties@1.2.1: + resolution: + { integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== } + engines: { node: '>= 0.4' } + + delaunator@5.0.1: + resolution: + { integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw== } + + delayed-stream@1.0.0: + resolution: + { integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== } + engines: { node: '>=0.4.0' } + + delegates@1.0.0: + resolution: + { integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== } + + dequal@2.0.3: + resolution: + { integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== } + engines: { node: '>=6' } + + detect-libc@2.0.4: + resolution: + { integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA== } + engines: { node: '>=8' } + + detect-node@2.1.0: + resolution: + { integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== } + + devlop@1.1.0: + resolution: + { integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== } + + dezalgo@1.0.4: + resolution: + { integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== } + + diff-match-patch@1.0.5: + resolution: + { integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw== } + + dingbat-to-unicode@1.0.1: + resolution: + { integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w== } + + dir-compare@3.3.0: + resolution: + { integrity: sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg== } + + dir-glob@3.0.1: + resolution: + { integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== } + engines: { node: '>=8' } + + dmg-builder@24.13.3: + resolution: + { integrity: sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ== } + + dmg-license@1.0.11: + resolution: + { integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q== } + engines: { node: '>=8' } + os: [darwin] + hasBin: true + + dom-helpers@5.2.1: + resolution: + { integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== } + + dompurify@3.2.6: + resolution: + { integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ== } + + dot-prop@5.3.0: + resolution: + { integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== } + engines: { node: '>=8' } + + dotenv-expand@5.1.0: + resolution: + { integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== } + + dotenv@9.0.2: + resolution: + { integrity: sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg== } + engines: { node: '>=10' } + + duck@0.1.12: + resolution: + { integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg== } + + dunder-proto@1.0.1: + resolution: + { integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== } + engines: { node: '>= 0.4' } + + eastasianwidth@0.2.0: + resolution: + { integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== } + + ecc-jsbn@0.1.2: + resolution: + { integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== } + + ejs@3.1.10: + resolution: + { integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== } + engines: { node: '>=0.10.0' } + hasBin: true + + electron-build@0.0.3: + resolution: + { integrity: sha512-gJ+Civsa9MfR4TssR1+XiJczYylYGoSD2mFMHcOimRV0JxeHA2bbqYJ1Usb9b61F+QuG6dN+l+wLGh7680zH+A== } + hasBin: true + + electron-builder-squirrel-windows@24.13.3: + resolution: + { integrity: sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg== } + + electron-builder@24.13.3: + resolution: + { integrity: sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg== } + engines: { node: '>=14.0.0' } + hasBin: true + + electron-publish@24.13.1: + resolution: + { integrity: sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A== } + + electron-to-chromium@1.5.166: + resolution: + { integrity: sha512-QPWqHL0BglzPYyJJ1zSSmwFFL6MFXhbACOCcsCdUMCkzPdS9/OIBVxg516X/Ado2qwAq8k0nJJ7phQPCqiaFAw== } + + electron-updater@6.6.2: + resolution: + { integrity: sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw== } + + electron@35.5.1: + resolution: + { integrity: sha512-kkbGXz56safvXcxqAZyMS2nJGYK9NFG/iKOJsAO5e0HPH8y3EnV4Fi87tvbfFpeLiaruFS+eN0YFy6f1StpSgQ== } + engines: { node: '>= 12.20.55' } + hasBin: true + + emoji-mart@5.6.0: + resolution: + { integrity: sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow== } + + emoji-regex@10.4.0: + resolution: + { integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== } + + emoji-regex@8.0.0: + resolution: + { integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== } + + emoji-regex@9.2.2: + resolution: + { integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== } + + end-of-stream@1.4.4: + resolution: + { integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== } + + entities@6.0.1: + resolution: + { integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== } + engines: { node: '>=0.12' } + + enumify@1.0.4: + resolution: + { integrity: sha512-5mwWXaVzJaqyUdOW/PDH5QySRgmQ8VvujmxmvXoXj9w0n+6omhVuyD56eI37FMqy/LxueJzsQ4DrHVQzuT/TXg== } + + env-paths@2.2.1: + resolution: + { integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== } + engines: { node: '>=6' } + + environment@1.1.0: + resolution: + { integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q== } + engines: { node: '>=18' } + + err-code@2.0.3: + resolution: + { integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA== } + + error-ex@1.3.2: + resolution: + { integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== } + + es-define-property@1.0.1: + resolution: + { integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== } + engines: { node: '>= 0.4' } + + es-errors@1.3.0: + resolution: + { integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== } + engines: { node: '>= 0.4' } + + es-object-atoms@1.1.1: + resolution: + { integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== } + engines: { node: '>= 0.4' } + + es-set-tostringtag@2.1.0: + resolution: + { integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== } + engines: { node: '>= 0.4' } + + es-toolkit@1.43.0: + resolution: + { integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA== } + + es6-error@4.1.1: + resolution: + { integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== } + + esast-util-from-estree@2.0.0: + resolution: + { integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ== } + + esast-util-from-js@2.0.1: + resolution: + { integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw== } + + escalade@3.2.0: + resolution: + { integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== } + engines: { node: '>=6' } + + escape-string-regexp@1.0.5: + resolution: + { integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== } + engines: { node: '>=0.8.0' } + + escape-string-regexp@4.0.0: + resolution: + { integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== } + engines: { node: '>=10' } + + escape-string-regexp@5.0.0: + resolution: + { integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== } + engines: { node: '>=12' } + + estree-util-attach-comments@3.0.0: + resolution: + { integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw== } + + estree-util-build-jsx@3.0.1: + resolution: + { integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ== } + + estree-util-is-identifier-name@3.0.0: + resolution: + { integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== } + + estree-util-scope@1.0.0: + resolution: + { integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ== } + + estree-util-to-js@2.0.0: + resolution: + { integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg== } + + estree-util-visit@2.0.0: + resolution: + { integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww== } + + estree-walker@3.0.3: + resolution: + { integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== } + + event-target-shim@5.0.1: + resolution: + { integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== } + engines: { node: '>=6' } + + eventemitter3@4.0.7: + resolution: + { integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== } + + eventemitter3@5.0.1: + resolution: + { integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== } + + eventsource-parser@3.0.2: + resolution: + { integrity: sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA== } + engines: { node: '>=18.0.0' } + + eventsource-parser@3.0.6: + resolution: + { integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== } + engines: { node: '>=18.0.0' } + + execa@8.0.1: + resolution: + { integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== } + engines: { node: '>=16.17' } + + expand-template@2.0.3: + resolution: + { integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== } + engines: { node: '>=6' } + + exsolve@1.0.5: + resolution: + { integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg== } + + extend-shallow@2.0.1: + resolution: + { integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== } + engines: { node: '>=0.10.0' } + + extend-shallow@3.0.2: + resolution: + { integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== } + engines: { node: '>=0.10.0' } + + extend@3.0.2: + resolution: + { integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== } + + extract-zip@2.0.1: + resolution: + { integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== } + engines: { node: '>= 10.17.0' } + hasBin: true + + extsprintf@1.3.0: + resolution: + { integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== } + engines: { '0': node >=0.6.0 } + + extsprintf@1.4.1: + resolution: + { integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA== } + engines: { '0': node >=0.6.0 } + + fast-deep-equal@3.1.3: + resolution: + { integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== } + + fast-glob@3.3.3: + resolution: + { integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== } + engines: { node: '>=8.6.0' } + + fast-json-stable-stringify@2.1.0: + resolution: + { integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== } + + fast-uri@3.0.6: + resolution: + { integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw== } + + fastq@1.19.1: + resolution: + { integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ== } + + fd-slicer@1.1.0: + resolution: + { integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== } + + file-selector@0.5.0: + resolution: + { integrity: sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA== } + engines: { node: '>= 10' } + + filelist@1.0.4: + resolution: + { integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== } + + fill-range@7.1.1: + resolution: + { integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== } + engines: { node: '>=8' } + + filter-obj@5.1.0: + resolution: + { integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng== } + engines: { node: '>=14.16' } + + find-root@1.1.0: + resolution: + { integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== } + + find-up@7.0.0: + resolution: + { integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g== } + engines: { node: '>=18' } + + follow-redirects@1.15.9: + resolution: + { integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== } + engines: { node: '>=4.0' } + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-in@1.0.2: + resolution: + { integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== } + engines: { node: '>=0.10.0' } + + foreground-child@3.3.1: + resolution: + { integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== } + engines: { node: '>=14' } + + forever-agent@0.6.1: + resolution: + { integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== } + + form-data-encoder@1.7.2: + resolution: + { integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A== } + + form-data@1.0.1: + resolution: + { integrity: sha512-M4Yhq2mLogpCtpUmfopFlTTuIe6mSCTgKvnlMhDj3NcgVhA1uS20jT0n+xunKPzpmL5w2erSVtp+SKiJf1TlWg== } + engines: { node: '>= 0.10' } + + form-data@4.0.3: + resolution: + { integrity: sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA== } + engines: { node: '>= 6' } + + formdata-node@4.4.1: + resolution: + { integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ== } + engines: { node: '>= 12.20' } + + formidable@3.5.4: + resolution: + { integrity: sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug== } + engines: { node: '>=14.0.0' } + + frac@1.1.2: + resolution: + { integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA== } + engines: { node: '>=0.8' } + + framer-motion@12.17.0: + resolution: + { integrity: sha512-2hISKgDk49yCLStwG1wf4Kdy/D6eBw9/eRNaWFIYoI9vMQ/Mqd1Fz+gzVlEtxJmtQ9y4IWnXm19/+UXD3dAYAA== } + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + from2@2.3.0: + resolution: + { integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== } + + fs-constants@1.0.0: + resolution: + { integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== } + + fs-extra@10.1.0: + resolution: + { integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== } + engines: { node: '>=12' } + + fs-extra@11.3.0: + resolution: + { integrity: sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew== } + engines: { node: '>=14.14' } + + fs-extra@8.1.0: + resolution: + { integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== } + engines: { node: '>=6 <7 || >=8' } + + fs-extra@9.1.0: + resolution: + { integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== } + engines: { node: '>=10' } + + fs-minipass@2.1.0: + resolution: + { integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== } + engines: { node: '>= 8' } + + fs.realpath@1.0.0: + resolution: + { integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== } + + function-bind@1.1.2: + resolution: + { integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== } + + gauge@3.0.2: + resolution: + { integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== } + engines: { node: '>=10' } + deprecated: This package is no longer supported. + + generate-function@2.3.1: + resolution: + { integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== } + + generate-object-property@1.2.0: + resolution: + { integrity: sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ== } + + gensync@1.0.0-beta.2: + resolution: + { integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== } + engines: { node: '>=6.9.0' } + + get-caller-file@2.0.5: + resolution: + { integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== } + engines: { node: 6.* || 8.* || >= 10.* } + + get-east-asian-width@1.3.0: + resolution: + { integrity: sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ== } + engines: { node: '>=18' } + + get-intrinsic@1.3.0: + resolution: + { integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== } + engines: { node: '>= 0.4' } + + get-proto@1.0.1: + resolution: + { integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== } + engines: { node: '>= 0.4' } + + get-stream@5.2.0: + resolution: + { integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== } + engines: { node: '>=8' } + + get-stream@8.0.1: + resolution: + { integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== } + engines: { node: '>=16' } + + get-value@2.0.6: + resolution: + { integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== } + engines: { node: '>=0.10.0' } + + getpass@0.1.7: + resolution: + { integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== } + + giscus@1.6.0: + resolution: + { integrity: sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ== } + + git-raw-commits@4.0.0: + resolution: + { integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ== } + engines: { node: '>=16' } + hasBin: true + + github-from-package@0.0.0: + resolution: + { integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw== } + + github-markdown-css@5.8.1: + resolution: + { integrity: sha512-8G+PFvqigBQSWLQjyzgpa2ThD9bo7+kDsriUIidGcRhXgmcaAWUIpCZf8DavJgc+xifjbCG+GvMyWr0XMXmc7g== } + engines: { node: '>=10' } + + glob-parent@5.1.2: + resolution: + { integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== } + engines: { node: '>= 6' } + + glob@10.4.5: + resolution: + { integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== } + hasBin: true + + glob@7.2.3: + resolution: + { integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== } + deprecated: Glob versions prior to v9 are no longer supported + + global-agent@3.0.0: + resolution: + { integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q== } + engines: { node: '>=10.0' } + + global-directory@4.0.1: + resolution: + { integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q== } + engines: { node: '>=18' } + + globals@11.12.0: + resolution: + { integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== } + engines: { node: '>=4' } + + globals@15.15.0: + resolution: + { integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg== } + engines: { node: '>=18' } + + globalthis@1.0.4: + resolution: + { integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ== } + engines: { node: '>= 0.4' } + + globby@11.1.0: + resolution: + { integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== } + engines: { node: '>=10' } + + gopd@1.2.0: + resolution: + { integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== } + engines: { node: '>= 0.4' } + + got@11.8.6: + resolution: + { integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g== } + engines: { node: '>=10.19.0' } + + graceful-fs@4.2.11: + resolution: + { integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== } + + graceful-readlink@1.0.1: + resolution: + { integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w== } + + hachure-fill@0.5.2: + resolution: + { integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg== } + + har-validator@2.0.6: + resolution: + { integrity: sha512-P6tFV+wCcUL3nbyTDAvveDySfbhy0XkDtAIfZP6HITjM2WUsiPna/Eg1Yy93SFXvahqoX+kt0n+6xlXKDXYowA== } + engines: { node: '>=0.10' } + deprecated: this library is no longer supported + hasBin: true + + has-ansi@2.0.0: + resolution: + { integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg== } + engines: { node: '>=0.10.0' } + + has-flag@4.0.0: + resolution: + { integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== } + engines: { node: '>=8' } + + has-property-descriptors@1.0.2: + resolution: + { integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== } + + has-symbols@1.1.0: + resolution: + { integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== } + engines: { node: '>= 0.4' } + + has-tostringtag@1.0.2: + resolution: + { integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== } + engines: { node: '>= 0.4' } + + has-unicode@2.0.1: + resolution: + { integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== } + + has@1.0.4: + resolution: + { integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== } + engines: { node: '>= 0.4.0' } + + hasown@2.0.2: + resolution: + { integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== } + engines: { node: '>= 0.4' } + + hast-util-from-dom@5.0.1: + resolution: + { integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q== } + + hast-util-from-html-isomorphic@2.0.0: + resolution: + { integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw== } + + hast-util-from-html@2.0.3: + resolution: + { integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw== } + + hast-util-from-parse5@8.0.3: + resolution: + { integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== } + + hast-util-is-element@3.0.0: + resolution: + { integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g== } + + hast-util-parse-selector@4.0.0: + resolution: + { integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== } + + hast-util-raw@9.1.0: + resolution: + { integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw== } + + hast-util-to-estree@3.1.3: + resolution: + { integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w== } + + hast-util-to-html@9.0.5: + resolution: + { integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== } + + hast-util-to-jsx-runtime@2.3.6: + resolution: + { integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== } + + hast-util-to-parse5@8.0.0: + resolution: + { integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw== } + + hast-util-to-text@4.0.2: + resolution: + { integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A== } + + hast-util-whitespace@3.0.0: + resolution: + { integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== } + + hastscript@9.0.1: + resolution: + { integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== } + + hawk@3.1.3: + resolution: + { integrity: sha512-X8xbmTc1cbPXcQV4WkLcRMALuyoxhfpFATmyuCxJPOAvrDS4DNnsTAOmKUxMTOWU6TzrTOkxPKwIx5ZOpJVSrg== } + engines: { node: '>=0.10.32' } + deprecated: This module moved to @hapi/hawk. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues. + + hoek@2.16.3: + resolution: + { integrity: sha512-V6Yw1rIcYV/4JsnggjBU0l4Kr+EXhpwqXRusENU1Xx6ro00IHPHYNynCuBTOZAPlr3AAmLvchH9I7N/VUdvOwQ== } + engines: { node: '>=0.10.40' } + deprecated: This version has been deprecated in accordance with the hapi support policy (hapi.im/support). Please upgrade to the latest version to get the best features, bug fixes, and security patches. If you are unable to upgrade at this time, paid support is available for older versions (hapi.im/commercial). + + hoist-non-react-statics@3.3.2: + resolution: + { integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== } + + hosted-git-info@4.1.0: + resolution: + { integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA== } + engines: { node: '>=10' } + + html-parse-stringify@3.0.1: + resolution: + { integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg== } + + html-url-attributes@3.0.1: + resolution: + { integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== } + + html-void-elements@3.0.0: + resolution: + { integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== } + + http-cache-semantics@4.2.0: + resolution: + { integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ== } + + http-proxy-agent@5.0.0: + resolution: + { integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== } + engines: { node: '>= 6' } + + http-signature@1.1.1: + resolution: + { integrity: sha512-iUn0NcRULlDGtqNLN1Jxmzayk8ogm7NToldASyZBpM2qggbphjXzNOiw3piN8tgz+e/DRs6X5gAzFwTI6BCRcg== } + engines: { node: '>=0.8', npm: '>=1.3.7' } + + http2-wrapper@1.0.3: + resolution: + { integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== } + engines: { node: '>=10.19.0' } + + https-proxy-agent@5.0.1: + resolution: + { integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== } + engines: { node: '>= 6' } + + human-signals@5.0.0: + resolution: + { integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== } + engines: { node: '>=16.17.0' } + + humanize-ms@1.2.1: + resolution: + { integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ== } + + husky@9.1.7: + resolution: + { integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA== } + engines: { node: '>=18' } + hasBin: true + + i18next-browser-languagedetector@8.2.0: + resolution: + { integrity: sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g== } + + i18next@24.2.3: + resolution: + { integrity: sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A== } + peerDependencies: + typescript: ^5 + peerDependenciesMeta: + typescript: + optional: true + + iconv-corefoundation@1.1.7: + resolution: + { integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ== } + engines: { node: ^8.11.2 || >=10 } + os: [darwin] + + iconv-lite@0.6.3: + resolution: + { integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== } + engines: { node: '>=0.10.0' } + + ieee754@1.2.1: + resolution: + { integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== } + + ignore@5.3.2: + resolution: + { integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== } + engines: { node: '>= 4' } + + image-size@2.0.2: + resolution: + { integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w== } + engines: { node: '>=16.x' } + hasBin: true + + immediate@3.0.6: + resolution: + { integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== } + + immer@10.1.1: + resolution: + { integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw== } + + immer@11.1.0: + resolution: + { integrity: sha512-dlzb07f5LDY+tzs+iLCSXV2yuhaYfezqyZQc+n6baLECWkOMEWxkECAOnXL0ba7lsA25fM9b2jtzpu/uxo1a7g== } + + import-fresh@3.3.1: + resolution: + { integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== } + engines: { node: '>=6' } + + import-meta-resolve@4.1.0: + resolution: + { integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw== } + + inflight@1.0.6: + resolution: + { integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== } + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.3: + resolution: + { integrity: sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== } + + inherits@2.0.4: + resolution: + { integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== } + + ini@1.3.8: + resolution: + { integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== } + + ini@4.1.1: + resolution: + { integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g== } + engines: { node: ^14.17.0 || ^16.13.0 || >=18.0.0 } + + inline-style-parser@0.2.4: + resolution: + { integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q== } + + internmap@1.0.1: + resolution: + { integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== } + + internmap@2.0.3: + resolution: + { integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg== } + engines: { node: '>=12' } + + intersection-observer@0.12.2: + resolution: + { integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg== } + + into-stream@6.0.0: + resolution: + { integrity: sha512-XHbaOAvP+uFKUFsOgoNPRjLkwB+I22JFPFe5OjTkQ0nwgj6+pSjb4NmB6VMxaPshLiOf+zcpOCBQuLwC1KHhZA== } + engines: { node: '>=10' } + + is-alphabetical@2.0.1: + resolution: + { integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== } + + is-alphanumerical@2.0.1: + resolution: + { integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== } + + is-arrayish@0.2.1: + resolution: + { integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== } + + is-arrayish@0.3.2: + resolution: + { integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== } + + is-ci@3.0.1: + resolution: + { integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== } + hasBin: true + + is-core-module@2.16.1: + resolution: + { integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w== } + engines: { node: '>= 0.4' } + + is-core-module@2.9.0: + resolution: + { integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== } + + is-decimal@2.0.1: + resolution: + { integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== } + + is-extendable@0.1.1: + resolution: + { integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== } + engines: { node: '>=0.10.0' } + + is-extendable@1.0.1: + resolution: + { integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== } + engines: { node: '>=0.10.0' } + + is-extglob@2.1.1: + resolution: + { integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== } + engines: { node: '>=0.10.0' } + + is-fullwidth-code-point@3.0.0: + resolution: + { integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== } + engines: { node: '>=8' } + + is-fullwidth-code-point@4.0.0: + resolution: + { integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== } + engines: { node: '>=12' } + + is-fullwidth-code-point@5.0.0: + resolution: + { integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA== } + engines: { node: '>=18' } + + is-glob@4.0.3: + resolution: + { integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== } + engines: { node: '>=0.10.0' } + + is-hexadecimal@2.0.1: + resolution: + { integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== } + + is-my-ip-valid@1.0.1: + resolution: + { integrity: sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg== } + + is-my-json-valid@2.20.6: + resolution: + { integrity: sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw== } + + is-number@7.0.0: + resolution: + { integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== } + engines: { node: '>=0.12.0' } + + is-obj@2.0.0: + resolution: + { integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== } + engines: { node: '>=8' } + + is-plain-obj@4.1.0: + resolution: + { integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== } + engines: { node: '>=12' } + + is-plain-object@2.0.4: + resolution: + { integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== } + engines: { node: '>=0.10.0' } + + is-property@1.0.2: + resolution: + { integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== } + + is-stream@3.0.0: + resolution: + { integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + is-text-path@2.0.0: + resolution: + { integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw== } + engines: { node: '>=8' } + + is-typedarray@1.0.0: + resolution: + { integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== } + + isarray@1.0.0: + resolution: + { integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== } + + isbinaryfile@4.0.10: + resolution: + { integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw== } + engines: { node: '>= 8.0.0' } + + isbinaryfile@5.0.4: + resolution: + { integrity: sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ== } + engines: { node: '>= 18.0.0' } + + isexe@2.0.0: + resolution: + { integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== } + + isobject@3.0.1: + resolution: + { integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== } + engines: { node: '>=0.10.0' } + + isstream@0.1.2: + resolution: + { integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== } + + jackspeak@3.4.3: + resolution: + { integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== } + + jake@10.9.2: + resolution: + { integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== } + engines: { node: '>=10' } + hasBin: true + + jiti@2.4.2: + resolution: + { integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A== } + hasBin: true + + joi@17.13.3: + resolution: + { integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA== } + + jotai@2.12.5: + resolution: + { integrity: sha512-G8m32HW3lSmcz/4mbqx0hgJIQ0ekndKWiYP7kWVKi0p6saLXdSoye+FZiOFyonnd7Q482LCzm8sMDl7Ar1NWDw== } + engines: { node: '>=12.20.0' } + peerDependencies: + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + js-cookie@3.0.5: + resolution: + { integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== } + engines: { node: '>=14' } + + js-tiktoken@1.0.20: + resolution: + { integrity: sha512-Xlaqhhs8VfCd6Sh7a1cFkZHQbYTLCwVJJWiHVxBYzLPxW0XsoxBy1hitmjkdIjD3Aon5BXLHFwU5O8WUx6HH+A== } + + js-tokens@4.0.0: + resolution: + { integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== } + + js-yaml@4.1.0: + resolution: + { integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== } + hasBin: true + + jsbn@0.1.1: + resolution: + { integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== } + + jsesc@2.5.2: + resolution: + { integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== } + engines: { node: '>=4' } + hasBin: true + + jsesc@3.1.0: + resolution: + { integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA== } + engines: { node: '>=6' } + hasBin: true + + json-buffer@3.0.1: + resolution: + { integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== } + + json-parse-even-better-errors@2.3.1: + resolution: + { integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== } + + json-schema-traverse@0.4.1: + resolution: + { integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== } + + json-schema-traverse@1.0.0: + resolution: + { integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== } + + json-schema@0.4.0: + resolution: + { integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== } + + json-stringify-safe@5.0.1: + resolution: + { integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== } + + json2mq@0.2.0: + resolution: + { integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA== } + + json5@2.2.3: + resolution: + { integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== } + engines: { node: '>=6' } + hasBin: true + + jsondiffpatch@0.6.0: + resolution: + { integrity: sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ== } + engines: { node: ^18.0.0 || >=20.0.0 } + hasBin: true + + jsonfile@4.0.0: + resolution: + { integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== } + + jsonfile@6.1.0: + resolution: + { integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== } + + jsonparse@1.3.1: + resolution: + { integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== } + engines: { '0': node >= 0.2.0 } + + jsonpointer@5.0.1: + resolution: + { integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== } + engines: { node: '>=0.10.0' } + + jsonrepair@3.13.1: + resolution: + { integrity: sha512-WJeiE0jGfxYmtLwBTEk8+y/mYcaleyLXWaqp5bJu0/ZTSeG0KQq/wWQ8pmnkKenEdN6pdnn6QtcoSUkbqDHWNw== } + hasBin: true + + jsprim@1.4.2: + resolution: + { integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw== } + engines: { node: '>=0.6.0' } + + jszip@2.5.0: + resolution: + { integrity: sha512-IRoyf8JSYY3nx+uyh5xPc0qdy8pUDTp2UkHOWYNF/IO/3D8nx7899UlSAjD8rf8wUgOmm0lACWx/GbW3EaxIXQ== } + + jszip@3.10.1: + resolution: + { integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== } + + katex@0.16.22: + resolution: + { integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg== } + hasBin: true + + keypress@0.2.1: + resolution: + { integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg== } + + keyv@4.5.4: + resolution: + { integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== } + + khroma@2.1.0: + resolution: + { integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw== } + + kolorist@1.8.0: + resolution: + { integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ== } + + langchain@0.3.28: + resolution: + { integrity: sha512-h4GGlBJNGU/Sj2PipW9kL+ewj7To3c+SnnNKH3HZaVHEqGPMHVB96T1lLjtCLcZCyUfabMr/zFIkLNI4War+Xg== } + engines: { node: '>=18' } + peerDependencies: + '@langchain/anthropic': '*' + '@langchain/aws': '*' + '@langchain/cerebras': '*' + '@langchain/cohere': '*' + '@langchain/core': '>=0.3.58 <0.4.0' + '@langchain/deepseek': '*' + '@langchain/google-genai': '*' + '@langchain/google-vertexai': '*' + '@langchain/google-vertexai-web': '*' + '@langchain/groq': '*' + '@langchain/mistralai': '*' + '@langchain/ollama': '*' + '@langchain/xai': '*' + axios: '*' + cheerio: '*' + handlebars: ^4.7.8 + peggy: ^3.0.2 + typeorm: '*' + peerDependenciesMeta: + '@langchain/anthropic': + optional: true + '@langchain/aws': + optional: true + '@langchain/cerebras': + optional: true + '@langchain/cohere': + optional: true + '@langchain/deepseek': + optional: true + '@langchain/google-genai': + optional: true + '@langchain/google-vertexai': + optional: true + '@langchain/google-vertexai-web': + optional: true + '@langchain/groq': + optional: true + '@langchain/mistralai': + optional: true + '@langchain/ollama': + optional: true + '@langchain/xai': + optional: true + axios: + optional: true + cheerio: + optional: true + handlebars: + optional: true + peggy: + optional: true + typeorm: + optional: true + + langium@3.3.1: + resolution: + { integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w== } + engines: { node: '>=16.0.0' } + + langsmith@0.3.31: + resolution: + { integrity: sha512-9lwuLZuN3tXFYQ6eMg0rmbBw7oxQo4bu1NYeylbjz27bOdG1XB9XNoxaiIArkK4ciLdOIOhPMBXP4bkvZOgHRw== } + peerDependencies: + openai: '*' + peerDependenciesMeta: + openai: + optional: true + + layout-base@1.0.2: + resolution: + { integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg== } + + layout-base@2.0.1: + resolution: + { integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg== } + + lazy-val@1.0.5: + resolution: + { integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q== } + + lazystream@1.0.1: + resolution: + { integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== } + engines: { node: '>= 0.6.3' } + + leva@0.10.0: + resolution: + { integrity: sha512-RiNJWmeqQdKIeHuVXgshmxIHu144a2AMYtLxKf8Nm1j93pisDPexuQDHKNdQlbo37wdyDQibLjY9JKGIiD7gaw== } + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + lie@3.3.0: + resolution: + { integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== } + + lilconfig@3.1.3: + resolution: + { integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw== } + engines: { node: '>=14' } + + lines-and-columns@1.2.4: + resolution: + { integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== } + + lint-staged@15.5.2: + resolution: + { integrity: sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w== } + engines: { node: '>=18.12.0' } + hasBin: true + + listr2@8.3.3: + resolution: + { integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ== } + engines: { node: '>=18.0.0' } + + lit-element@4.2.0: + resolution: + { integrity: sha512-MGrXJVAI5x+Bfth/pU9Kst1iWID6GHDLEzFEnyULB/sFiRLgkd8NPK/PeeXxktA3T6EIIaq8U3KcbTU5XFcP2Q== } + + lit-html@3.3.0: + resolution: + { integrity: sha512-RHoswrFAxY2d8Cf2mm4OZ1DgzCoBKUKSPvA1fhtSELxUERq2aQQ2h05pO9j81gS1o7RIRJ+CePLogfyahwmynw== } + + lit@3.3.0: + resolution: + { integrity: sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw== } + + local-pkg@1.1.1: + resolution: + { integrity: sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg== } + engines: { node: '>=14' } + + locate-path@7.2.0: + resolution: + { integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + lodash-es@4.17.21: + resolution: + { integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== } + + lodash.camelcase@4.3.0: + resolution: + { integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== } + + lodash.debounce@4.0.8: + resolution: + { integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== } + + lodash.defaults@4.2.0: + resolution: + { integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== } + + lodash.difference@4.5.0: + resolution: + { integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== } + + lodash.escaperegexp@4.1.2: + resolution: + { integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== } + + lodash.flatten@4.4.0: + resolution: + { integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== } + + lodash.isequal@4.5.0: + resolution: + { integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== } + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + + lodash.isplainobject@4.0.6: + resolution: + { integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== } + + lodash.kebabcase@4.1.1: + resolution: + { integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g== } + + lodash.merge@4.6.2: + resolution: + { integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== } + + lodash.mergewith@4.6.2: + resolution: + { integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== } + + lodash.snakecase@4.1.1: + resolution: + { integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw== } + + lodash.startcase@4.4.0: + resolution: + { integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg== } + + lodash.union@4.6.0: + resolution: + { integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== } + + lodash.uniq@4.5.0: + resolution: + { integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== } + + lodash.upperfirst@4.3.1: + resolution: + { integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg== } + + lodash@4.17.21: + resolution: + { integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== } + + log-update@6.1.0: + resolution: + { integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== } + engines: { node: '>=18' } + + longest-streak@3.1.0: + resolution: + { integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== } + + loose-envify@1.4.0: + resolution: + { integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== } + hasBin: true + + lop@0.4.2: + resolution: + { integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw== } + + lowercase-keys@2.0.0: + resolution: + { integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== } + engines: { node: '>=8' } + + lru-cache@10.4.3: + resolution: + { integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== } + + lru-cache@5.1.1: + resolution: + { integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== } + + lru-cache@6.0.0: + resolution: + { integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== } + engines: { node: '>=10' } + + lucide-react@0.469.0: + resolution: + { integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw== } + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + lucide-react@0.484.0: + resolution: + { integrity: sha512-oZy8coK9kZzvqhSgfbGkPtTgyjpBvs3ukLgDPv14dSOZtBtboryWF5o8i3qen7QbGg7JhiJBz5mK1p8YoMZTLQ== } + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + make-dir@3.1.0: + resolution: + { integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== } + engines: { node: '>=8' } + + mammoth@1.9.1: + resolution: + { integrity: sha512-4S2v1eP4Yo4so0zGNicJKcP93su3wDPcUk+xvkjSG75nlNjSkDJu8BhWQ+e54BROM0HfA6nPzJn12S6bq2Ko6w== } + engines: { node: '>=12.0.0' } + hasBin: true + + markdown-extensions@2.0.0: + resolution: + { integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q== } + engines: { node: '>=16' } + + markdown-table@3.0.4: + resolution: + { integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw== } + + marked@15.0.12: + resolution: + { integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA== } + engines: { node: '>= 18' } + hasBin: true + + matcher@3.0.0: + resolution: + { integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng== } + engines: { node: '>=10' } + + math-intrinsics@1.1.0: + resolution: + { integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== } + engines: { node: '>= 0.4' } + + mdast-util-find-and-replace@3.0.2: + resolution: + { integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== } + + mdast-util-from-markdown@2.0.2: + resolution: + { integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== } + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: + { integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== } + + mdast-util-gfm-footnote@2.1.0: + resolution: + { integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== } + + mdast-util-gfm-strikethrough@2.0.0: + resolution: + { integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== } + + mdast-util-gfm-table@2.0.0: + resolution: + { integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== } + + mdast-util-gfm-task-list-item@2.0.0: + resolution: + { integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== } + + mdast-util-gfm@3.1.0: + resolution: + { integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== } + + mdast-util-math@3.0.0: + resolution: + { integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w== } + + mdast-util-mdx-expression@2.0.1: + resolution: + { integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== } + + mdast-util-mdx-jsx@3.2.0: + resolution: + { integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== } + + mdast-util-mdx@3.0.0: + resolution: + { integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w== } + + mdast-util-mdxjs-esm@2.0.1: + resolution: + { integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== } + + mdast-util-newline-to-break@2.0.0: + resolution: + { integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog== } + + mdast-util-phrasing@4.1.0: + resolution: + { integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== } + + mdast-util-to-hast@13.2.0: + resolution: + { integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA== } + + mdast-util-to-markdown@2.1.2: + resolution: + { integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== } + + mdast-util-to-string@4.0.0: + resolution: + { integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== } + + meow@12.1.1: + resolution: + { integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw== } + engines: { node: '>=16.10' } + + merge-stream@2.0.0: + resolution: + { integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== } + + merge-value@1.0.0: + resolution: + { integrity: sha512-fJMmvat4NeKz63Uv9iHWcPDjCWcCkoiRoajRTEO8hlhUC6rwaHg0QCF9hBOTjZmm4JuglPckPSTtcuJL5kp0TQ== } + engines: { node: '>=0.10.0' } + + merge2@1.4.1: + resolution: + { integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== } + engines: { node: '>= 8' } + + mermaid@11.6.0: + resolution: + { integrity: sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg== } + + micromark-core-commonmark@2.0.3: + resolution: + { integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== } + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: + { integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== } + + micromark-extension-gfm-footnote@2.1.0: + resolution: + { integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== } + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: + { integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== } + + micromark-extension-gfm-table@2.1.1: + resolution: + { integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== } + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: + { integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== } + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: + { integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== } + + micromark-extension-gfm@3.0.0: + resolution: + { integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== } + + micromark-extension-math@3.1.0: + resolution: + { integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg== } + + micromark-extension-mdx-expression@3.0.1: + resolution: + { integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q== } + + micromark-extension-mdx-jsx@3.0.2: + resolution: + { integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ== } + + micromark-extension-mdx-md@2.0.0: + resolution: + { integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ== } + + micromark-extension-mdxjs-esm@3.0.0: + resolution: + { integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A== } + + micromark-extension-mdxjs@3.0.0: + resolution: + { integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ== } + + micromark-factory-destination@2.0.1: + resolution: + { integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== } + + micromark-factory-label@2.0.1: + resolution: + { integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== } + + micromark-factory-mdx-expression@2.0.3: + resolution: + { integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ== } + + micromark-factory-space@2.0.1: + resolution: + { integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== } + + micromark-factory-title@2.0.1: + resolution: + { integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== } + + micromark-factory-whitespace@2.0.1: + resolution: + { integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== } + + micromark-util-character@2.1.1: + resolution: + { integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== } + + micromark-util-chunked@2.0.1: + resolution: + { integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== } + + micromark-util-classify-character@2.0.1: + resolution: + { integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== } + + micromark-util-combine-extensions@2.0.1: + resolution: + { integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== } + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: + { integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== } + + micromark-util-decode-string@2.0.1: + resolution: + { integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== } + + micromark-util-encode@2.0.1: + resolution: + { integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== } + + micromark-util-events-to-acorn@2.0.3: + resolution: + { integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg== } + + micromark-util-html-tag-name@2.0.1: + resolution: + { integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== } + + micromark-util-normalize-identifier@2.0.1: + resolution: + { integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== } + + micromark-util-resolve-all@2.0.1: + resolution: + { integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== } + + micromark-util-sanitize-uri@2.0.1: + resolution: + { integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== } + + micromark-util-subtokenize@2.1.0: + resolution: + { integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== } + + micromark-util-symbol@2.0.1: + resolution: + { integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== } + + micromark-util-types@2.0.2: + resolution: + { integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== } + + micromark@4.0.2: + resolution: + { integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== } + + micromatch@4.0.8: + resolution: + { integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== } + engines: { node: '>=8.6' } + + mime-db@1.52.0: + resolution: + { integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== } + engines: { node: '>= 0.6' } + + mime-types@2.1.35: + resolution: + { integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== } + engines: { node: '>= 0.6' } + + mime@2.6.0: + resolution: + { integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== } + engines: { node: '>=4.0.0' } + hasBin: true + + mimic-fn@4.0.0: + resolution: + { integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== } + engines: { node: '>=12' } + + mimic-function@5.0.1: + resolution: + { integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA== } + engines: { node: '>=18' } + + mimic-response@1.0.1: + resolution: + { integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== } + engines: { node: '>=4' } + + mimic-response@2.1.0: + resolution: + { integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== } + engines: { node: '>=8' } + + mimic-response@3.1.0: + resolution: + { integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== } + engines: { node: '>=10' } + + minimatch@3.1.2: + resolution: + { integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== } + + minimatch@5.1.6: + resolution: + { integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== } + engines: { node: '>=10' } + + minimatch@9.0.5: + resolution: + { integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== } + engines: { node: '>=16 || 14 >=14.17' } + + minimist@1.2.8: + resolution: + { integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== } + + minipass@3.3.6: + resolution: + { integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw== } + engines: { node: '>=8' } + + minipass@5.0.0: + resolution: + { integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== } + engines: { node: '>=8' } + + minipass@7.1.2: + resolution: + { integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== } + engines: { node: '>=16 || 14 >=14.17' } + + minizlib@2.1.2: + resolution: + { integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== } + engines: { node: '>= 8' } + + mixin-deep@1.3.2: + resolution: + { integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== } + engines: { node: '>=0.10.0' } + + mkdirp-classic@0.5.3: + resolution: + { integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== } + + mkdirp@1.0.4: + resolution: + { integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== } + engines: { node: '>=10' } + hasBin: true + + mlly@1.7.4: + resolution: + { integrity: sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw== } + + motion-dom@12.17.0: + resolution: + { integrity: sha512-FA6/c70R9NKs3g41XDVONzmUUrEmyaifLVGCWtAmHP0usDnX9W+RN/tmbC4EUl0w6yLGvMTOwnWCFVgA5luhRg== } + + motion-utils@12.12.1: + resolution: + { integrity: sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w== } + + ms@2.1.3: + resolution: + { integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== } + + multistream@4.1.0: + resolution: + { integrity: sha512-J1XDiAmmNpRCBfIWJv+n0ymC4ABcf/Pl+5YvC5B/D2f/2+8PtHvCNxMPKiQcZyi922Hq69J2YOpb1pTywfifyw== } + + mustache@4.2.0: + resolution: + { integrity: sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== } + hasBin: true + + nan@2.22.2: + resolution: + { integrity: sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ== } + + nanoid@3.3.11: + resolution: + { integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== } + engines: { node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1 } + hasBin: true + + nanoid@5.1.5: + resolution: + { integrity: sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw== } + engines: { node: ^18 || >=20 } + hasBin: true + + napi-build-utils@1.0.2: + resolution: + { integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg== } + + next-themes@0.2.1: + resolution: + { integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A== } + peerDependencies: + next: '*' + react: '*' + react-dom: '*' + + next@14.2.29: + resolution: + { integrity: sha512-s98mCOMOWLGGpGOfgKSnleXLuegvvH415qtRZXpSp00HeEgdmrxmwL9cgKU+h4XrhB16zEI5d/7BnkS3ATInsA== } + engines: { node: '>=18.17.0' } + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + + node-abi@3.75.0: + resolution: + { integrity: sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg== } + engines: { node: '>=10' } + + node-addon-api@1.7.2: + resolution: + { integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== } + + node-domexception@1.0.0: + resolution: + { integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== } + engines: { node: '>=10.5.0' } + deprecated: Use your platform's native DOMException instead + + node-fetch@2.7.0: + resolution: + { integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== } + engines: { node: 4.x || >=6.0.0 } + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-releases@2.0.19: + resolution: + { integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== } + + node-uuid@1.4.8: + resolution: + { integrity: sha512-TkCET/3rr9mUuRp+CpO7qfgT++aAxfDRaalQhwPFzI9BY/2rCDn6OfpZOVggi1AXfTPpfkTrg5f5WQx5G1uLxA== } + deprecated: Use uuid module instead + hasBin: true + + nopt@5.0.0: + resolution: + { integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== } + engines: { node: '>=6' } + hasBin: true + + normalize-path@3.0.0: + resolution: + { integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== } + engines: { node: '>=0.10.0' } + + normalize-url@6.1.0: + resolution: + { integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== } + engines: { node: '>=10' } + + npm-run-path@5.3.0: + resolution: + { integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + npmlog@5.0.1: + resolution: + { integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== } + deprecated: This package is no longer supported. + + numeral@2.0.6: + resolution: + { integrity: sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA== } + + oauth-sign@0.8.2: + resolution: + { integrity: sha512-VlF07iu3VV3+BTXj43Nmp6Irt/G7j/NgEctUS6IweH1RGhURjjCc2NWtzXFPXXWWfc7hgbXQdtiQu2LGp6MxUg== } + + object-assign@4.1.1: + resolution: + { integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== } + engines: { node: '>=0.10.0' } + + object-keys@1.1.1: + resolution: + { integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== } + engines: { node: '>= 0.4' } + + ollama-ai-provider@1.2.0: + resolution: + { integrity: sha512-jTNFruwe3O/ruJeppI/quoOUxG7NA6blG3ZyQj3lei4+NnJo7bi3eIRWqlVpRlu/mbzbFXeJSBuYQWF6pzGKww== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.0.0 + peerDependenciesMeta: + zod: + optional: true + + on-change@4.0.2: + resolution: + { integrity: sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + once@1.4.0: + resolution: + { integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== } + + onetime@6.0.0: + resolution: + { integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== } + engines: { node: '>=12' } + + onetime@7.0.0: + resolution: + { integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ== } + engines: { node: '>=18' } + + oniguruma-parser@0.12.1: + resolution: + { integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w== } + + oniguruma-to-es@4.3.3: + resolution: + { integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg== } + + openai@4.104.0: + resolution: + { integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA== } + hasBin: true + peerDependencies: + ws: ^8.18.0 + zod: ^3.23.8 + peerDependenciesMeta: + ws: + optional: true + zod: + optional: true + + openapi-types@12.1.3: + resolution: + { integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw== } + + opener@1.5.2: + resolution: + { integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== } + hasBin: true + + option@0.2.4: + resolution: + { integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A== } + + p-cancelable@2.1.1: + resolution: + { integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== } + engines: { node: '>=8' } + + p-finally@1.0.0: + resolution: + { integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== } + engines: { node: '>=4' } + + p-is-promise@3.0.0: + resolution: + { integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ== } + engines: { node: '>=8' } + + p-limit@4.0.0: + resolution: + { integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + p-locate@6.0.0: + resolution: + { integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + p-queue@6.6.2: + resolution: + { integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== } + engines: { node: '>=8' } + + p-retry@4.6.2: + resolution: + { integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== } + engines: { node: '>=8' } + + p-timeout@3.2.0: + resolution: + { integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== } + engines: { node: '>=8' } + + package-json-from-dist@1.0.1: + resolution: + { integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== } + + package-manager-detector@1.3.0: + resolution: + { integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ== } + + pako@0.2.9: + resolution: + { integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== } + + pako@1.0.11: + resolution: + { integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== } + + parent-module@1.0.1: + resolution: + { integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== } + engines: { node: '>=6' } + + parse-entities@4.0.2: + resolution: + { integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== } + + parse-json@5.2.0: + resolution: + { integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== } + engines: { node: '>=8' } + + parse5@7.3.0: + resolution: + { integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== } + + partial-json@0.1.7: + resolution: + { integrity: sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA== } + + path-data-parser@0.1.0: + resolution: + { integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w== } + + path-exists@5.0.0: + resolution: + { integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + path-is-absolute@1.0.1: + resolution: + { integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== } + engines: { node: '>=0.10.0' } + + path-key@3.1.1: + resolution: + { integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== } + engines: { node: '>=8' } + + path-key@4.0.0: + resolution: + { integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== } + engines: { node: '>=12' } + + path-parse@1.0.7: + resolution: + { integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== } + + path-scurry@1.11.1: + resolution: + { integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== } + engines: { node: '>=16 || 14 >=14.18' } + + path-type@4.0.0: + resolution: + { integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== } + engines: { node: '>=8' } + + path@0.12.7: + resolution: + { integrity: sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q== } + + pathe@2.0.3: + resolution: + { integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== } + + pdf2md-js@1.0.8: + resolution: + { integrity: sha512-cjKv46RzWmUNCt0mOgr/HGOJJl1fJmNOaldx+tdwlxTiZojZVBkGwI27HLPbms5zH+XRdJiCwjynnZZuKQygvQ== } + engines: { node: '>=18' } + + pend@1.2.0: + resolution: + { integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== } + + picocolors@1.1.1: + resolution: + { integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== } + + picomatch@2.3.1: + resolution: + { integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== } + engines: { node: '>=8.6' } + + pidtree@0.6.0: + resolution: + { integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g== } + engines: { node: '>=0.10' } + hasBin: true + + pinkie-promise@2.0.1: + resolution: + { integrity: sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== } + engines: { node: '>=0.10.0' } + + pinkie@2.0.4: + resolution: + { integrity: sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== } + engines: { node: '>=0.10.0' } + + pkg-fetch@3.4.2: + resolution: + { integrity: sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA== } + hasBin: true + + pkg-types@1.3.1: + resolution: + { integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ== } + + pkg-types@2.1.0: + resolution: + { integrity: sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A== } + + pkg@5.8.1: + resolution: + { integrity: sha512-CjBWtFStCfIiT4Bde9QpJy0KeH19jCfwZRJqHFDFXfhUklCx8JoFmMj3wgnEYIwGmZVNkhsStPHEOnrtrQhEXA== } + hasBin: true + peerDependencies: + node-notifier: '>=9.0.1' + peerDependenciesMeta: + node-notifier: + optional: true + + plist@3.1.0: + resolution: + { integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ== } + engines: { node: '>=10.4.0' } + + points-on-curve@0.2.0: + resolution: + { integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A== } + + points-on-path@0.2.1: + resolution: + { integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g== } + + polished@4.3.1: + resolution: + { integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA== } + engines: { node: '>=10' } + + postcss@8.4.31: + resolution: + { integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== } + engines: { node: ^10 || ^12 || >=14 } + + prebuild-install@7.1.1: + resolution: + { integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== } + engines: { node: '>=10' } + hasBin: true + + prisma@6.9.0: + resolution: + { integrity: sha512-resJAwMyZREC/I40LF6FZ6rZTnlrlrYrb63oW37Gq+U+9xHwbyMSPJjKtM7VZf3gTO86t/Oyz+YeSXr3CmAY1Q== } + engines: { node: '>=18.18' } + hasBin: true + peerDependencies: + typescript: '>=5.1.0' + peerDependenciesMeta: + typescript: + optional: true + + process-nextick-args@1.0.7: + resolution: + { integrity: sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw== } + + process-nextick-args@2.0.1: + resolution: + { integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== } + + process@0.11.10: + resolution: + { integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== } + engines: { node: '>= 0.6.0' } + + progress@1.1.8: + resolution: + { integrity: sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw== } + engines: { node: '>=0.4.0' } + + progress@2.0.3: + resolution: + { integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== } + engines: { node: '>=0.4.0' } + + promise-retry@2.0.1: + resolution: + { integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g== } + engines: { node: '>=10' } + + prop-types@15.8.1: + resolution: + { integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== } + + property-information@6.5.0: + resolution: + { integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== } + + property-information@7.1.0: + resolution: + { integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== } + + proxy-from-env@1.1.0: + resolution: + { integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== } + + pump@3.0.2: + resolution: + { integrity: sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw== } + + punycode@1.3.2: + resolution: + { integrity: sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== } + + punycode@2.3.1: + resolution: + { integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== } + engines: { node: '>=6' } + + q@1.4.1: + resolution: + { integrity: sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg== } + engines: { node: '>=0.6.0', teleport: '>=0.2.0' } + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + + qs@6.1.2: + resolution: + { integrity: sha512-vkyEo9cSlcgr1xj5n14ykoPWKE36R8wkxK2fQkbGACZNv4zDGFw/juEwFFUs9/APU7DaTMRlRNTYISLPD0Z4Qw== } + engines: { node: '>=0.6' } + + quansync@0.2.10: + resolution: + { integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A== } + + query-string@9.2.0: + resolution: + { integrity: sha512-YIRhrHujoQxhexwRLxfy3VSjOXmvZRd2nyw1PwL1UUqZ/ys1dEZd1+NSgXkne2l/4X/7OXkigEAuhTX0g/ivJQ== } + engines: { node: '>=18' } + + querystring@0.2.0: + resolution: + { integrity: sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== } + engines: { node: '>=0.4.x' } + deprecated: The querystring API is considered Legacy. new code should use the URLSearchParams API instead. + + queue-microtask@1.2.3: + resolution: + { integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== } + + quick-lru@5.1.1: + resolution: + { integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== } + engines: { node: '>=10' } + + rc-cascader@3.34.0: + resolution: + { integrity: sha512-KpXypcvju9ptjW9FaN2NFcA2QH9E9LHKq169Y0eWtH4e/wHQ5Wh5qZakAgvb8EKZ736WZ3B0zLLOBsrsja5Dag== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-checkbox@3.5.0: + resolution: + { integrity: sha512-aOAQc3E98HteIIsSqm6Xk2FPKIER6+5vyEFMZfo73TqM+VVAIqOkHoPjgKLqSNtVLWScoaM7vY2ZrGEheI79yg== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-collapse@3.9.0: + resolution: + { integrity: sha512-swDdz4QZ4dFTo4RAUMLL50qP0EY62N2kvmk2We5xYdRwcRn8WcYtuetCJpwpaCbUfUt5+huLpVxhvmnK+PHrkA== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-collapse@4.0.0: + resolution: + { integrity: sha512-SwoOByE39/3oIokDs/BnkqI+ltwirZbP8HZdq1/3SkPSBi7xDdvWHTp7cpNI9ullozkR6mwTWQi6/E/9huQVrA== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dialog@9.6.0: + resolution: + { integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-drawer@7.3.0: + resolution: + { integrity: sha512-DX6CIgiBWNpJIMGFO8BAISFkxiuKitoizooj4BDyee8/SnBn0zwO2FHrNDpqqepj0E/TFTDpmEBCyFuTgC7MOg== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-dropdown@4.2.1: + resolution: + { integrity: sha512-YDAlXsPv3I1n42dv1JpdM7wJ+gSUBfeyPK59ZpBD9jQhK9jVuxpjj3NmWQHOBceA1zEPVX84T2wbdb2SD0UjmA== } + peerDependencies: + react: '>=16.11.0' + react-dom: '>=16.11.0' + + rc-field-form@2.7.0: + resolution: + { integrity: sha512-hgKsCay2taxzVnBPZl+1n4ZondsV78G++XVsMIJCAoioMjlMQR9YwAp7JZDIECzIu2Z66R+f4SFIRrO2DjDNAA== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-footer@0.6.8: + resolution: + { integrity: sha512-JBZ+xcb6kkex8XnBd4VHw1ZxjV6kmcwUumSHaIFdka2qzMCo7Klcy4sI6G0XtUpG/vtpislQCc+S9Bc+NLHYMg== } + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-image@7.12.0: + resolution: + { integrity: sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input-number@9.5.0: + resolution: + { integrity: sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-input@1.8.0: + resolution: + { integrity: sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA== } + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-mentions@2.20.0: + resolution: + { integrity: sha512-w8HCMZEh3f0nR8ZEd466ATqmXFCMGMN5UFCzEUL0bM/nGw/wOS2GgRzKBcm19K++jDyuWCOJOdgcKGXU3fXfbQ== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-menu@9.16.1: + resolution: + { integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-motion@2.9.5: + resolution: + { integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-notification@5.6.4: + resolution: + { integrity: sha512-KcS4O6B4qzM3KH7lkwOB7ooLPZ4b6J+VMmQgT51VZCeEcmghdeR4IrMcFq0LG+RPdnbe/ArT086tGM8Snimgiw== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-overflow@1.4.1: + resolution: + { integrity: sha512-3MoPQQPV1uKyOMVNd6SZfONi+f3st0r8PksexIdBTeIYbMX0Jr+k7pHEDvsXtR4BpCv90/Pv2MovVNhktKrwvw== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-pagination@5.1.0: + resolution: + { integrity: sha512-8416Yip/+eclTFdHXLKTxZvn70duYVGTvUUWbckCCZoIl3jagqke3GLsFrMs0bsQBikiYpZLD9206Ej4SOdOXQ== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-picker@4.11.3: + resolution: + { integrity: sha512-MJ5teb7FlNE0NFHTncxXQ62Y5lytq6sh5nUw0iH8OkHL/TjARSEvSHpr940pWgjGANpjCwyMdvsEV55l5tYNSg== } + engines: { node: '>=8.x' } + peerDependencies: + date-fns: '>= 2.x' + dayjs: '>= 1.x' + luxon: '>= 3.x' + moment: '>= 2.x' + react: '>=16.9.0' + react-dom: '>=16.9.0' + peerDependenciesMeta: + date-fns: + optional: true + dayjs: + optional: true + luxon: + optional: true + moment: + optional: true + + rc-progress@4.0.0: + resolution: + { integrity: sha512-oofVMMafOCokIUIBnZLNcOZFsABaUw8PPrf1/y0ZBvKZNpOiu5h4AO9vv11Sw0p4Hb3D0yGWuEattcQGtNJ/aw== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-rate@2.13.1: + resolution: + { integrity: sha512-QUhQ9ivQ8Gy7mtMZPAjLbxBt5y9GRp65VcUyGUMF3N3fhiftivPHdpuDIaWIMOTEprAjZPC08bls1dQB+I1F2Q== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-resize-observer@1.4.3: + resolution: + { integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-segmented@2.7.0: + resolution: + { integrity: sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA== } + peerDependencies: + react: '>=16.0.0' + react-dom: '>=16.0.0' + + rc-select@14.16.8: + resolution: + { integrity: sha512-NOV5BZa1wZrsdkKaiK7LHRuo5ZjZYMDxPP6/1+09+FB4KoNi8jcG1ZqLE3AVCxEsYMBe65OBx71wFoHRTP3LRg== } + engines: { node: '>=8.x' } + peerDependencies: + react: '*' + react-dom: '*' + + rc-slider@11.1.8: + resolution: + { integrity: sha512-2gg/72YFSpKP+Ja5AjC5DPL1YnV8DEITDQrcc1eASrUYjl0esptaBVJBh5nLTXCCp15eD8EuGjwezVGSHhs9tQ== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-steps@6.0.1: + resolution: + { integrity: sha512-lKHL+Sny0SeHkQKKDJlAjV5oZ8DwCdS2hFhAkIjuQt1/pB81M0cA0ErVFdHq9+jmPmFw1vJB2F5NBzFXLJxV+g== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-switch@4.1.0: + resolution: + { integrity: sha512-TI8ufP2Az9oEbvyCeVE4+90PDSljGyuwix3fV58p7HV2o4wBnVToEyomJRVyTaZeqNPAp+vqeo4Wnj5u0ZZQBg== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-table@7.51.0: + resolution: + { integrity: sha512-7ZlvW6lB0IDKaSFInD6OfJsCepSJJtfsQv2PZLtzEeZd/PLzQnKliXPaoZqkqDdLdJ3jxE2x4sane4DjxcAg+g== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tabs@15.6.1: + resolution: + { integrity: sha512-/HzDV1VqOsUWyuC0c6AkxVYFjvx9+rFPKZ32ejxX0Uc7QCzcEjTA9/xMgv4HemPKwzBNX8KhGVbbumDjnj92aA== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-textarea@1.10.0: + resolution: + { integrity: sha512-ai9IkanNuyBS4x6sOL8qu/Ld40e6cEs6pgk93R+XLYg0mDSjNBGey6/ZpDs5+gNLD7urQ14po3V6Ck2dJLt9SA== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tooltip@6.4.0: + resolution: + { integrity: sha512-kqyivim5cp8I5RkHmpsp1Nn/Wk+1oeloMv9c7LXNgDxUpGm+RbXJGL+OPvDlcRnx9DBeOe4wyOIl4OKUERyH1g== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-tree-select@5.27.0: + resolution: + { integrity: sha512-2qTBTzwIT7LRI1o7zLyrCzmo5tQanmyGbSaGTIf7sYimCklAToVVfpMC6OAldSKolcnjorBYPNSKQqJmN3TCww== } + peerDependencies: + react: '*' + react-dom: '*' + + rc-tree@5.13.1: + resolution: + { integrity: sha512-FNhIefhftobCdUJshO7M8uZTA9F4OPGVXqGfZkkD/5soDeOhwO06T/aKTrg0WD8gRg/pyfq+ql3aMymLHCTC4A== } + engines: { node: '>=10.x' } + peerDependencies: + react: '*' + react-dom: '*' + + rc-upload@4.9.2: + resolution: + { integrity: sha512-nHx+9rbd1FKMiMRYsqQ3NkXUv7COHPBo3X1Obwq9SWS6/diF/A0aJ5OHubvwUAIDs+4RMleljV0pcrNUc823GQ== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-util@5.44.4: + resolution: + { integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w== } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc-virtual-list@3.18.6: + resolution: + { integrity: sha512-TQ5SsutL3McvWmmxqQtMIbfeoE3dGjJrRSfKekgby7WQMpPIFvv4ghytp5Z0s3D8Nik9i9YNOCqHBfk86AwgAA== } + engines: { node: '>=8.x' } + peerDependencies: + react: '>=16.9.0' + react-dom: '>=16.9.0' + + rc@1.2.8: + resolution: + { integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== } + hasBin: true + + re-resizable@6.11.2: + resolution: + { integrity: sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A== } + peerDependencies: + react: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-avatar-editor@13.0.2: + resolution: + { integrity: sha512-a4ajbi7lwDh98kgEtSEeKMu0vs0CHTczkq4Xcxr1EiwMFH1GlgHCEtwGU8q/H5W8SeLnH4KPK8LUjEEaZXklxQ== } + peerDependencies: + react: ^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + + react-colorful@5.6.1: + resolution: + { integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw== } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + react-dom@18.3.1: + resolution: + { integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== } + peerDependencies: + react: ^18.3.1 + + react-draggable@4.4.6: + resolution: + { integrity: sha512-LtY5Xw1zTPqHkVmtM3X8MUOxNDOUhv/khTgBgrUvwaS064bwVvxT+q5El0uUFNx5IEPKXuRejr7UqLwBIg5pdw== } + peerDependencies: + react: '>= 16.3.0' + react-dom: '>= 16.3.0' + + react-dropzone@12.1.0: + resolution: + { integrity: sha512-iBYHA1rbopIvtzokEX4QubO6qk5IF/x3BtKGu74rF2JkQDXnwC4uO/lHKpaw4PJIV6iIAYOlwLv2FpiGyqHNog== } + engines: { node: '>= 10.13' } + peerDependencies: + react: '>= 16.8' + + react-error-boundary@5.0.0: + resolution: + { integrity: sha512-tnjAxG+IkpLephNcePNA7v6F/QpWLH8He65+DmedchDwg162JZqx4NmbXj0mlAYVVEd81OW7aFhmbsScYfiAFQ== } + peerDependencies: + react: '>=16.13.1' + + react-fast-compare@3.2.2: + resolution: + { integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== } + + react-hotkeys-hook@5.1.0: + resolution: + { integrity: sha512-GCNGXjBzV9buOS3REoQFmSmE4WTvBhYQ0YrAeeMZI83bhXg3dRWsLHXDutcVDdEjwJqJCxk5iewWYX5LtFUd7g== } + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + react-i18next@15.5.2: + resolution: + { integrity: sha512-ePODyXgmZQAOYTbZXQn5rRsSBu3Gszo69jxW6aKmlSgxKAI1fOhDwSu6bT4EKHciWPKQ7v7lPrjeiadR6Gi+1A== } + peerDependencies: + i18next: '>= 23.2.3' + react: '>= 16.8.0' + react-dom: '*' + react-native: '*' + typescript: ^5 + peerDependenciesMeta: + react-dom: + optional: true + react-native: + optional: true + typescript: + optional: true + + react-is@16.13.1: + resolution: + { integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== } + + react-is@18.3.1: + resolution: + { integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== } + + react-is@19.1.0: + resolution: + { integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg== } + + react-layout-kit@1.9.1: + resolution: + { integrity: sha512-tQO5J+Ajppu2JCdhgFaFbWCg01WJXXaQ5vg8cxzsv8vVeogJKGFgoJm9OI2saDFchfKP3RABd+aRY5vB++poqw== } + peerDependencies: + react: '>=18' + + react-markdown@10.1.0: + resolution: + { integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== } + peerDependencies: + '@types/react': '>=18' + react: '>=18' + + react-merge-refs@3.0.2: + resolution: + { integrity: sha512-MSZAfwFfdbEvwkKWP5EI5chuLYnNUxNS7vyS0i1Jp+wtd8J4Ga2ddzhaE68aMol2Z4vCnRM/oGOo1a3V75UPlw== } + peerDependencies: + react: '>=16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0' + peerDependenciesMeta: + react: + optional: true + + react-redux@9.2.0: + resolution: + { integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g== } + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-rnd@10.5.2: + resolution: + { integrity: sha512-0Tm4x7k7pfHf2snewJA8x7Nwgt3LV+58MVEWOVsFjk51eYruFEa6Wy7BNdxt4/lH0wIRsu7Gm3KjSXY2w7YaNw== } + peerDependencies: + react: '>=16.3.0' + react-dom: '>=16.3.0' + + react-transition-group@4.4.5: + resolution: + { integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== } + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react-zoom-pan-pinch@3.7.0: + resolution: + { integrity: sha512-UmReVZ0TxlKzxSbYiAj+LeGRW8s8LraAFTXRAxzMYnNRgGPsxCudwZKVkjvGmjtx7SW/hZamt69NUmGf4xrkXA== } + engines: { node: '>=8', npm: '>=5' } + peerDependencies: + react: '*' + react-dom: '*' + + react@18.3.1: + resolution: + { integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== } + engines: { node: '>=0.10.0' } + + read-config-file@6.3.2: + resolution: + { integrity: sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q== } + engines: { node: '>=12.0.0' } + + readable-stream@2.0.6: + resolution: + { integrity: sha512-TXcFfb63BQe1+ySzsHZI/5v1aJPCShfqvWJ64ayNImXMsN1Cd0YGk/wm8KB7/OeessgPc9QvS9Zou8QTkFzsLw== } + + readable-stream@2.3.8: + resolution: + { integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== } + + readable-stream@3.6.2: + resolution: + { integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== } + engines: { node: '>= 6' } + + readdir-glob@1.1.3: + resolution: + { integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== } + + recharts@3.6.0: + resolution: + { integrity: sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg== } + engines: { node: '>=18' } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + recma-build-jsx@1.0.0: + resolution: + { integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew== } + + recma-jsx@1.0.0: + resolution: + { integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q== } + + recma-parse@1.0.0: + resolution: + { integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ== } + + recma-stringify@1.0.0: + resolution: + { integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g== } + + redux-thunk@3.1.0: + resolution: + { integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw== } + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: + { integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== } + + regex-recursion@6.0.2: + resolution: + { integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg== } + + regex-utilities@2.3.0: + resolution: + { integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng== } + + regex@6.0.1: + resolution: + { integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA== } + + rehype-katex@7.0.1: + resolution: + { integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA== } + + rehype-raw@7.0.0: + resolution: + { integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== } + + rehype-recma@1.0.0: + resolution: + { integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw== } + + remark-breaks@4.0.0: + resolution: + { integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ== } + + remark-gfm@4.0.1: + resolution: + { integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== } + + remark-math@6.0.0: + resolution: + { integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA== } + + remark-mdx@3.1.0: + resolution: + { integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA== } + + remark-parse@11.0.0: + resolution: + { integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== } + + remark-rehype@11.1.2: + resolution: + { integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== } + + remark-stringify@11.0.0: + resolution: + { integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== } + + request@2.72.0: + resolution: + { integrity: sha512-rQiQ3Eza3HNC+gBlzKxXaPwG1rQIcO0/7TKGIgA9D/obvFK//H+pzkCS4CctQ7aFk6LboTvyFXHMEdf8P4pSxg== } + engines: { node: '>=0.8.0' } + deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 + + require-directory@2.1.1: + resolution: + { integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== } + engines: { node: '>=0.10.0' } + + require-from-string@2.0.2: + resolution: + { integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== } + engines: { node: '>=0.10.0' } + + reselect@5.1.1: + resolution: + { integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w== } + + resize-observer-polyfill@1.5.1: + resolution: + { integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg== } + + resolve-alpn@1.2.1: + resolution: + { integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== } + + resolve-from@4.0.0: + resolution: + { integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== } + engines: { node: '>=4' } + + resolve-from@5.0.0: + resolution: + { integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== } + engines: { node: '>=8' } + + resolve@1.22.10: + resolution: + { integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== } + engines: { node: '>= 0.4' } + hasBin: true + + responselike@2.0.1: + resolution: + { integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== } + + restore-cursor@5.1.0: + resolution: + { integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA== } + engines: { node: '>=18' } + + retry@0.12.0: + resolution: + { integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== } + engines: { node: '>= 4' } + + retry@0.13.1: + resolution: + { integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== } + engines: { node: '>= 4' } + + reusify@1.1.0: + resolution: + { integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== } + engines: { iojs: '>=1.0.0', node: '>=0.10.0' } + + rfdc@1.4.1: + resolution: + { integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== } + + rimraf@3.0.2: + resolution: + { integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== } + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + roarr@2.15.4: + resolution: + { integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A== } + engines: { node: '>=8.0' } + + robust-predicates@3.0.2: + resolution: + { integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg== } + + roughjs@4.6.6: + resolution: + { integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ== } + + run-parallel@1.2.0: + resolution: + { integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== } + + rw@1.3.3: + resolution: + { integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ== } + + rxjs@7.8.2: + resolution: + { integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== } + + safe-buffer@5.1.2: + resolution: + { integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== } + + safe-buffer@5.2.1: + resolution: + { integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== } + + safer-buffer@2.1.2: + resolution: + { integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== } + + sanitize-filename@1.6.3: + resolution: + { integrity: sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg== } + + sax@1.4.1: + resolution: + { integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== } + + scheduler@0.23.2: + resolution: + { integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ== } + + screenfull@5.2.0: + resolution: + { integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA== } + engines: { node: '>=0.10.0' } + + scroll-into-view-if-needed@3.1.0: + resolution: + { integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ== } + + secure-json-parse@2.7.0: + resolution: + { integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== } + + semver-compare@1.0.0: + resolution: + { integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow== } + + semver@6.3.1: + resolution: + { integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== } + hasBin: true + + semver@7.7.2: + resolution: + { integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA== } + engines: { node: '>=10' } + hasBin: true + + serialize-error@7.0.1: + resolution: + { integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw== } + engines: { node: '>=10' } + + set-blocking@2.0.0: + resolution: + { integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== } + + set-value@2.0.1: + resolution: + { integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== } + engines: { node: '>=0.10.0' } + + setimmediate@1.0.5: + resolution: + { integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== } + + sharp@0.33.5: + resolution: + { integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw== } + engines: { node: ^18.17.0 || ^20.3.0 || >=21.0.0 } + + shebang-command@2.0.0: + resolution: + { integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== } + engines: { node: '>=8' } + + shebang-regex@3.0.0: + resolution: + { integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== } + engines: { node: '>=8' } + + shell-quote@1.8.3: + resolution: + { integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== } + engines: { node: '>= 0.4' } + + shiki@3.6.0: + resolution: + { integrity: sha512-tKn/Y0MGBTffQoklaATXmTqDU02zx8NYBGQ+F6gy87/YjKbizcLd+Cybh/0ZtOBX9r1NEnAy/GTRDKtOsc1L9w== } + + signal-exit@3.0.7: + resolution: + { integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== } + + signal-exit@4.1.0: + resolution: + { integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== } + engines: { node: '>=14' } + + simple-concat@1.0.1: + resolution: + { integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== } + + simple-get@3.1.1: + resolution: + { integrity: sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA== } + + simple-get@4.0.1: + resolution: + { integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== } + + simple-swizzle@0.2.2: + resolution: + { integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg== } + + simple-update-notifier@2.0.0: + resolution: + { integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w== } + engines: { node: '>=10' } + + simple-wcswidth@1.0.1: + resolution: + { integrity: sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg== } + + slash@3.0.0: + resolution: + { integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== } + engines: { node: '>=8' } + + slice-ansi@3.0.0: + resolution: + { integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== } + engines: { node: '>=8' } + + slice-ansi@5.0.0: + resolution: + { integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== } + engines: { node: '>=12' } + + slice-ansi@7.1.0: + resolution: + { integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg== } + engines: { node: '>=18' } + + smart-buffer@4.2.0: + resolution: + { integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== } + engines: { node: '>= 6.0.0', npm: '>= 3.0.0' } + + sntp@1.0.9: + resolution: + { integrity: sha512-7bgVOAnPj3XjrKY577S+puCKGCRlUrcrEdsMeRXlg9Ghf5df/xNi6sONUa43WrHUd3TjJBF7O04jYoiY0FVa0A== } + engines: { node: '>=0.8.0' } + deprecated: This module moved to @hapi/sntp. Please make sure to switch over as this distribution is no longer supported and may contain bugs and critical security issues. + + sonner@2.0.5: + resolution: + { integrity: sha512-YwbHQO6cSso3HBXlbCkgrgzDNIhws14r4MO87Ofy+cV2X7ES4pOoAK3+veSmVTvqNx1BWUxlhPmZzP00Crk2aQ== } + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + + source-map-js@1.2.1: + resolution: + { integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== } + engines: { node: '>=0.10.0' } + + source-map-support@0.5.21: + resolution: + { integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== } + + source-map@0.5.7: + resolution: + { integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== } + engines: { node: '>=0.10.0' } + + source-map@0.6.1: + resolution: + { integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== } + engines: { node: '>=0.10.0' } + + source-map@0.7.4: + resolution: + { integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== } + engines: { node: '>= 8' } + + space-separated-tokens@2.0.2: + resolution: + { integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== } + + spawn-command@0.0.2: + resolution: + { integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ== } + + split-on-first@3.0.0: + resolution: + { integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA== } + engines: { node: '>=12' } + + split-string@3.1.0: + resolution: + { integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== } + engines: { node: '>=0.10.0' } + + split2@4.2.0: + resolution: + { integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== } + engines: { node: '>= 10.x' } + + sprintf-js@1.0.3: + resolution: + { integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== } + + sprintf-js@1.1.3: + resolution: + { integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA== } + + ssf@0.11.2: + resolution: + { integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g== } + engines: { node: '>=0.8' } + + sshpk@1.18.0: + resolution: + { integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== } + engines: { node: '>=0.10.0' } + hasBin: true + + stat-mode@1.0.0: + resolution: + { integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg== } + engines: { node: '>= 6' } + + stream-meter@1.0.4: + resolution: + { integrity: sha512-4sOEtrbgFotXwnEuzzsQBYEV1elAeFSO8rSGeTwabuX1RRn/kEq9JVH7I0MRBhKVRR0sJkr0M0QCH7yOLf9fhQ== } + + streamsearch@1.1.0: + resolution: + { integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== } + engines: { node: '>=10.0.0' } + + string-argv@0.3.2: + resolution: + { integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q== } + engines: { node: '>=0.6.19' } + + string-convert@0.2.1: + resolution: + { integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== } + + string-width@4.2.3: + resolution: + { integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== } + engines: { node: '>=8' } + + string-width@5.1.2: + resolution: + { integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== } + engines: { node: '>=12' } + + string-width@7.2.0: + resolution: + { integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== } + engines: { node: '>=18' } + + string_decoder@0.10.31: + resolution: + { integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== } + + string_decoder@1.1.1: + resolution: + { integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== } + + string_decoder@1.3.0: + resolution: + { integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== } + + stringify-entities@4.0.4: + resolution: + { integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== } + + stringstream@0.0.6: + resolution: + { integrity: sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== } + + strip-ansi@3.0.1: + resolution: + { integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== } + engines: { node: '>=0.10.0' } + + strip-ansi@6.0.1: + resolution: + { integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== } + engines: { node: '>=8' } + + strip-ansi@7.1.0: + resolution: + { integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== } + engines: { node: '>=12' } + + strip-final-newline@3.0.0: + resolution: + { integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== } + engines: { node: '>=12' } + + strip-json-comments@2.0.1: + resolution: + { integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== } + engines: { node: '>=0.10.0' } + + style-to-js@1.1.16: + resolution: + { integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw== } + + style-to-object@1.0.8: + resolution: + { integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g== } + + styled-jsx@5.1.1: + resolution: + { integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw== } + engines: { node: '>= 12.0.0' } + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylis@4.2.0: + resolution: + { integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== } + + stylis@4.3.6: + resolution: + { integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ== } + + sumchecker@3.0.1: + resolution: + { integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg== } + engines: { node: '>= 8.0' } + + supports-color@2.0.0: + resolution: + { integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g== } + engines: { node: '>=0.8.0' } + + supports-color@7.2.0: + resolution: + { integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== } + engines: { node: '>=8' } + + supports-color@8.1.1: + resolution: + { integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== } + engines: { node: '>=10' } + + supports-preserve-symlinks-flag@1.0.0: + resolution: + { integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== } + engines: { node: '>= 0.4' } + + swr@2.3.3: + resolution: + { integrity: sha512-dshNvs3ExOqtZ6kJBaAsabhPdHyeY4P2cKwRCniDVifBMoG/SVI7tfLWqPXriVspf2Rg4tPzXJTnwaihIeFw2A== } + peerDependencies: + react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + tabbable@6.2.0: + resolution: + { integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== } + + tar-fs@2.1.3: + resolution: + { integrity: sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg== } + + tar-stream@2.2.0: + resolution: + { integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== } + engines: { node: '>=6' } + + tar@6.2.1: + resolution: + { integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== } + engines: { node: '>=10' } + + temp-file@3.4.0: + resolution: + { integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg== } + + text-extensions@2.4.0: + resolution: + { integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g== } + engines: { node: '>=8' } + + throttle-debounce@5.0.2: + resolution: + { integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A== } + engines: { node: '>=12.22' } + + throttleit@2.1.0: + resolution: + { integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw== } + engines: { node: '>=18' } + + through@2.3.8: + resolution: + { integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== } + + tiny-invariant@1.3.3: + resolution: + { integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== } + + tiny-typed-emitter@2.1.0: + resolution: + { integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA== } + + tinyexec@1.0.1: + resolution: + { integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw== } + + tmp-promise@3.0.3: + resolution: + { integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ== } + + tmp@0.2.3: + resolution: + { integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== } + engines: { node: '>=14.14' } + + to-fast-properties@2.0.0: + resolution: + { integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== } + engines: { node: '>=4' } + + to-regex-range@5.0.1: + resolution: + { integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== } + engines: { node: '>=8.0' } + + toggle-selection@1.0.6: + resolution: + { integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ== } + + tough-cookie@2.2.2: + resolution: + { integrity: sha512-Knz9Yr0hlBoWQgUKzOIvRg5adinizAf49i2gHRhj6cLjlM304zRw7uyiY22ADniDxnPHXfIeyQD0EAkgpIz0ow== } + engines: { node: '>=0.10.0' } + deprecated: ReDoS vulnerability parsing Set-Cookie https://nodesecurity.io/advisories/130 + + tr46@0.0.3: + resolution: + { integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== } + + tree-kill@1.2.2: + resolution: + { integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== } + hasBin: true + + trim-lines@3.0.1: + resolution: + { integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== } + + trough@2.2.0: + resolution: + { integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== } + + truncate-utf8-bytes@1.0.2: + resolution: + { integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ== } + + ts-dedent@2.2.0: + resolution: + { integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== } + engines: { node: '>=6.10' } + + ts-md5@1.3.1: + resolution: + { integrity: sha512-DiwiXfwvcTeZ5wCE0z+2A9EseZsztaiZtGrtSaY5JOD7ekPnR/GoIVD5gXZAlK9Na9Kvpo9Waz5rW64WKAWApg== } + engines: { node: '>=12' } + + tslib@2.6.2: + resolution: + { integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== } + + tslib@2.8.1: + resolution: + { integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== } + + tunnel-agent@0.4.3: + resolution: + { integrity: sha512-e0IoVDWx8SDHc/hwFTqJDQ7CCDTEeGhmcT9jkWJjoGQSpgBz20nAMr80E3Tpk7PatJ1b37DQDgJR3CNSzcMOZQ== } + + tunnel-agent@0.6.0: + resolution: + { integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== } + + turndown@7.2.0: + resolution: + { integrity: sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A== } + + tweetnacl@0.14.5: + resolution: + { integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== } + + type-fest@0.13.1: + resolution: + { integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg== } + engines: { node: '>=10' } + + typescript@5.8.3: + resolution: + { integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== } + engines: { node: '>=14.17' } + hasBin: true + + ufo@1.6.1: + resolution: + { integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA== } + + underscore@1.13.7: + resolution: + { integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g== } + + undici-types@5.26.5: + resolution: + { integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== } + + undici-types@6.21.0: + resolution: + { integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ== } + + undici-types@7.8.0: + resolution: + { integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw== } + + unicorn-magic@0.1.0: + resolution: + { integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ== } + engines: { node: '>=18' } + + unified@11.0.5: + resolution: + { integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== } + + unist-util-find-after@5.0.0: + resolution: + { integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ== } + + unist-util-is@6.0.0: + resolution: + { integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== } + + unist-util-position-from-estree@2.0.0: + resolution: + { integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ== } + + unist-util-position@5.0.0: + resolution: + { integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== } + + unist-util-remove-position@5.0.0: + resolution: + { integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q== } + + unist-util-stringify-position@4.0.0: + resolution: + { integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== } + + unist-util-visit-parents@6.0.1: + resolution: + { integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== } + + unist-util-visit@5.0.0: + resolution: + { integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== } + + universalify@0.1.2: + resolution: + { integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== } + engines: { node: '>= 4.0.0' } + + universalify@2.0.1: + resolution: + { integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== } + engines: { node: '>= 10.0.0' } + + unpdf@0.12.2: + resolution: + { integrity: sha512-3eyDFfayk+Sf5+inJ4OyhecR2BtRFEeZqUfGPdq2O8aBLau9MYL9lAP+GEcSAaVd2JWqde8Dnz38z0x7KRglaA== } + + update-browserslist-db@1.1.3: + resolution: + { integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw== } + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: + { integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== } + + url-join@5.0.0: + resolution: + { integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA== } + engines: { node: ^12.20.0 || ^14.13.1 || >=16.0.0 } + + url@0.11.0: + resolution: + { integrity: sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== } + + use-isomorphic-layout-effect@1.2.1: + resolution: + { integrity: sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA== } + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + use-merge-value@1.2.0: + resolution: + { integrity: sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw== } + peerDependencies: + react: '>= 16.x' + + use-sync-external-store@1.5.0: + resolution: + { integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== } + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + utf8-byte-length@1.0.5: + resolution: + { integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== } + + util-deprecate@1.0.2: + resolution: + { integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== } + + util@0.10.4: + resolution: + { integrity: sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== } + + uuid@10.0.0: + resolution: + { integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== } + hasBin: true + + uuid@11.1.0: + resolution: + { integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A== } + hasBin: true + + v8n@1.5.1: + resolution: + { integrity: sha512-LdabyT4OffkyXFCe9UT+uMkxNBs5rcTVuZClvxQr08D5TUgo1OFKkoT65qYRCsiKBl/usHjpXvP4hHMzzDRj3A== } + + verror@1.10.0: + resolution: + { integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== } + engines: { '0': node >=0.6.0 } + + verror@1.10.1: + resolution: + { integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg== } + engines: { node: '>=0.6.0' } + + vfile-location@5.0.3: + resolution: + { integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== } + + vfile-message@4.0.2: + resolution: + { integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== } + + vfile@6.0.3: + resolution: + { integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== } + + victory-vendor@37.3.6: + resolution: + { integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ== } + + void-elements@3.1.0: + resolution: + { integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w== } + engines: { node: '>=0.10.0' } + + vscode-jsonrpc@8.2.0: + resolution: + { integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA== } + engines: { node: '>=14.0.0' } + + vscode-languageserver-protocol@3.17.5: + resolution: + { integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg== } + + vscode-languageserver-textdocument@1.0.12: + resolution: + { integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA== } + + vscode-languageserver-types@3.17.5: + resolution: + { integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg== } + + vscode-languageserver@9.0.1: + resolution: + { integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g== } + hasBin: true + + vscode-uri@3.0.8: + resolution: + { integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== } + + wait-on@7.2.0: + resolution: + { integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ== } + engines: { node: '>=12.0.0' } + hasBin: true + + web-namespaces@2.0.1: + resolution: + { integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== } + + web-streams-polyfill@4.0.0-beta.3: + resolution: + { integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug== } + engines: { node: '>= 14' } + + webidl-conversions@3.0.1: + resolution: + { integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== } + + whatwg-url@5.0.0: + resolution: + { integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== } + + which@2.0.2: + resolution: + { integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== } + engines: { node: '>= 8' } + hasBin: true + + wide-align@1.1.5: + resolution: + { integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== } + + wmf@1.0.2: + resolution: + { integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw== } + engines: { node: '>=0.8' } + + word@0.3.0: + resolution: + { integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA== } + engines: { node: '>=0.8' } + + wrap-ansi@7.0.0: + resolution: + { integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== } + engines: { node: '>=10' } + + wrap-ansi@8.1.0: + resolution: + { integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== } + engines: { node: '>=12' } + + wrap-ansi@9.0.0: + resolution: + { integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q== } + engines: { node: '>=18' } + + wrappy@1.0.2: + resolution: + { integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== } + + xlsx@0.18.5: + resolution: + { integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ== } + engines: { node: '>=0.8' } + hasBin: true + + xmlbuilder@10.1.1: + resolution: + { integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg== } + engines: { node: '>=4.0' } + + xmlbuilder@15.1.1: + resolution: + { integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== } + engines: { node: '>=8.0' } + + xmldom@0.6.0: + resolution: + { integrity: sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== } + engines: { node: '>=10.0.0' } + + xtend@4.0.2: + resolution: + { integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== } + engines: { node: '>=0.4' } + + y18n@5.0.8: + resolution: + { integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== } + engines: { node: '>=10' } + + yallist@3.1.1: + resolution: + { integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== } + + yallist@4.0.0: + resolution: + { integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== } + + yaml@1.10.2: + resolution: + { integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== } + engines: { node: '>= 6' } + + yaml@2.8.0: + resolution: + { integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ== } + engines: { node: '>= 14.6' } + hasBin: true + + yargs-parser@20.2.9: + resolution: + { integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== } + engines: { node: '>=10' } + + yargs-parser@21.1.1: + resolution: + { integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== } + engines: { node: '>=12' } + + yargs@16.2.0: + resolution: + { integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== } + engines: { node: '>=10' } + + yargs@17.7.2: + resolution: + { integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== } + engines: { node: '>=12' } + + yauzl@2.10.0: + resolution: + { integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== } + + yocto-queue@1.2.1: + resolution: + { integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg== } + engines: { node: '>=12.20' } + + zhipu-ai-provider@0.1.1: + resolution: + { integrity: sha512-cVwvvGtPiQqgsGdBzHCHC5oQ7z6slEQTbXJ5+42gQGX4N5uRUvYj+YYLp7Cr1HPQGF3zR2p8vNbT5etPHD4NbA== } + engines: { node: '>=18' } + peerDependencies: + zod: ^3.0.0 + + zip-stream@4.1.1: + resolution: + { integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== } + engines: { node: '>= 10' } + + zod-to-json-schema@3.24.5: + resolution: + { integrity: sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== } + peerDependencies: + zod: ^3.24.1 + + zod@3.25.32: + resolution: + { integrity: sha512-OSm2xTIRfW8CV5/QKgngwmQW/8aPfGdaQFlrGoErlgg/Epm7cjb6K6VEyExfe65a3VybUOnu381edLb0dfJl0g== } + + zod@3.25.76: + resolution: + { integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== } + + zustand@3.7.2: + resolution: + { integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA== } + engines: { node: '>=12.7.0' } + peerDependencies: + react: '>=16.8' + peerDependenciesMeta: + react: + optional: true + + zwitch@2.0.4: + resolution: + { integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== } + +snapshots: + 7zip-bin@5.2.0: {} + + '@ai-sdk/openai-compatible@1.0.22(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@ai-sdk/provider-utils': 3.0.12(zod@3.25.76) + zod: 3.25.76 + + '@ai-sdk/openai@1.3.22(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 + + '@ai-sdk/provider-utils@2.1.10(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.0.9 + eventsource-parser: 3.0.2 + nanoid: 3.3.11 + secure-json-parse: 2.7.0 + optionalDependencies: + zod: 3.25.76 + + '@ai-sdk/provider-utils@2.2.8(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + nanoid: 3.3.11 + secure-json-parse: 2.7.0 + zod: 3.25.76 + + '@ai-sdk/provider-utils@3.0.12(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 2.0.0 + '@standard-schema/spec': 1.0.0 + eventsource-parser: 3.0.6 + zod: 3.25.76 + + '@ai-sdk/provider@1.0.9': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/provider@1.1.3': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/provider@2.0.0': + dependencies: + json-schema: 0.4.0 + + '@ai-sdk/react@1.2.12(react@18.3.1)(zod@3.25.76)': + dependencies: + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + react: 18.3.1 + swr: 2.3.3(react@18.3.1) + throttleit: 2.1.0 + optionalDependencies: + zod: 3.25.76 + + '@ai-sdk/ui-utils@1.2.11(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + + '@ant-design/colors@7.2.1': + dependencies: + '@ant-design/fast-color': 2.0.6 + + '@ant-design/cssinjs-utils@1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.27.6 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/cssinjs@1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/hash': 0.8.0 + '@emotion/unitless': 0.7.5 + classnames: 2.5.1 + csstype: 3.1.3 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + stylis: 4.3.6 + + '@ant-design/fast-color@2.0.6': + dependencies: + '@babel/runtime': 7.27.6 + + '@ant-design/icons-svg@4.4.2': {} + + '@ant-design/icons@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/colors': 7.2.1 + '@ant-design/icons-svg': 4.4.2 + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@ant-design/react-slick@1.1.2(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + json2mq: 0.2.0 + react: 18.3.1 + resize-observer-polyfill: 1.5.1 + throttle-debounce: 5.0.2 + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.3.0 + tinyexec: 1.0.1 + + '@antfu/utils@8.1.1': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.27.5': {} + + '@babel/core@7.27.4': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.27.4) + '@babel/helpers': 7.27.6 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.18.2': + dependencies: + '@babel/types': 7.19.0 + '@jridgewell/gen-mapping': 0.3.8 + jsesc: 2.5.2 + + '@babel/generator@7.27.5': + dependencies: + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + '@jridgewell/gen-mapping': 0.3.8 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + debug: 4.4.1 + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.27.4 + '@babel/types': 7.27.6 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.27.4 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.27.6': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + + '@babel/parser@7.18.4': + dependencies: + '@babel/types': 7.19.0 + + '@babel/parser@7.27.5': + dependencies: + '@babel/types': 7.27.6 + + '@babel/plugin-transform-runtime@7.27.4(@babel/core@7.27.4)': + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + babel-plugin-polyfill-corejs2: 0.4.13(@babel/core@7.27.4) + babel-plugin-polyfill-corejs3: 0.11.1(@babel/core@7.27.4) + babel-plugin-polyfill-regenerator: 0.6.4(@babel/core@7.27.4) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.27.6': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.27.5 + '@babel/types': 7.27.6 + + '@babel/traverse@7.27.4': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.27.5 + '@babel/parser': 7.27.5 + '@babel/template': 7.27.2 + '@babel/types': 7.27.6 + debug: 4.4.1 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.19.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + to-fast-properties: 2.0.0 + + '@babel/types@7.27.6': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@braintree/sanitize-url@7.1.1': {} + + '@cfworker/json-schema@4.1.1': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + + '@commitlint/cli@19.8.1(@types/node@24.0.1)(typescript@5.8.3)': + dependencies: + '@commitlint/format': 19.8.1 + '@commitlint/lint': 19.8.1 + '@commitlint/load': 19.8.1(@types/node@24.0.1)(typescript@5.8.3) + '@commitlint/read': 19.8.1 + '@commitlint/types': 19.8.1 + tinyexec: 1.0.1 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/config-conventional@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + conventional-changelog-conventionalcommits: 7.0.2 + + '@commitlint/config-validator@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + ajv: 8.17.1 + + '@commitlint/ensure@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + lodash.camelcase: 4.3.0 + lodash.kebabcase: 4.1.1 + lodash.snakecase: 4.1.1 + lodash.startcase: 4.4.0 + lodash.upperfirst: 4.3.1 + + '@commitlint/execute-rule@19.8.1': {} + + '@commitlint/format@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + chalk: 5.4.1 + + '@commitlint/is-ignored@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + semver: 7.7.2 + + '@commitlint/lint@19.8.1': + dependencies: + '@commitlint/is-ignored': 19.8.1 + '@commitlint/parse': 19.8.1 + '@commitlint/rules': 19.8.1 + '@commitlint/types': 19.8.1 + + '@commitlint/load@19.8.1(@types/node@24.0.1)(typescript@5.8.3)': + dependencies: + '@commitlint/config-validator': 19.8.1 + '@commitlint/execute-rule': 19.8.1 + '@commitlint/resolve-extends': 19.8.1 + '@commitlint/types': 19.8.1 + chalk: 5.4.1 + cosmiconfig: 9.0.0(typescript@5.8.3) + cosmiconfig-typescript-loader: 6.1.0(@types/node@24.0.1)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3) + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + lodash.uniq: 4.5.0 + transitivePeerDependencies: + - '@types/node' + - typescript + + '@commitlint/message@19.8.1': {} + + '@commitlint/parse@19.8.1': + dependencies: + '@commitlint/types': 19.8.1 + conventional-changelog-angular: 7.0.0 + conventional-commits-parser: 5.0.0 + + '@commitlint/read@19.8.1': + dependencies: + '@commitlint/top-level': 19.8.1 + '@commitlint/types': 19.8.1 + git-raw-commits: 4.0.0 + minimist: 1.2.8 + tinyexec: 1.0.1 + + '@commitlint/resolve-extends@19.8.1': + dependencies: + '@commitlint/config-validator': 19.8.1 + '@commitlint/types': 19.8.1 + global-directory: 4.0.1 + import-meta-resolve: 4.1.0 + lodash.mergewith: 4.6.2 + resolve-from: 5.0.0 + + '@commitlint/rules@19.8.1': + dependencies: + '@commitlint/ensure': 19.8.1 + '@commitlint/message': 19.8.1 + '@commitlint/to-lines': 19.8.1 + '@commitlint/types': 19.8.1 + + '@commitlint/to-lines@19.8.1': {} + + '@commitlint/top-level@19.8.1': + dependencies: + find-up: 7.0.0 + + '@commitlint/types@19.8.1': + dependencies: + '@types/conventional-commits-parser': 5.0.1 + chalk: 5.4.1 + + '@develar/schema-utils@2.6.5': + dependencies: + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + '@dnd-kit/accessibility@3.1.1(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/accessibility': 3.1.1(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + + '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)': + dependencies: + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + react: 18.3.1 + tslib: 2.8.1 + + '@dnd-kit/utilities@3.2.2(react@18.3.1)': + dependencies: + react: 18.3.1 + tslib: 2.8.1 + + '@electron/asar@3.4.1': + dependencies: + commander: 5.1.0 + glob: 7.2.3 + minimatch: 3.1.2 + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.1 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@electron/notarize@2.2.1': + dependencies: + debug: 4.4.1 + fs-extra: 9.1.0 + promise-retry: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@electron/osx-sign@1.0.5': + dependencies: + compare-version: 0.1.2 + debug: 4.4.1 + fs-extra: 10.1.0 + isbinaryfile: 4.0.10 + minimist: 1.2.8 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@electron/universal@1.5.1': + dependencies: + '@electron/asar': 3.4.1 + '@malept/cross-spawn-promise': 1.1.1 + debug: 4.4.1 + dir-compare: 3.3.0 + fs-extra: 9.1.0 + minimatch: 3.1.2 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emoji-mart/data@1.2.1': {} + + '@emoji-mart/react@1.1.1(emoji-mart@5.6.0)(react@18.3.1)': + dependencies: + emoji-mart: 5.6.0 + react: 18.3.1 + + '@emotion/babel-plugin@11.13.5': + dependencies: + '@babel/helper-module-imports': 7.27.1 + '@babel/runtime': 7.27.6 + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/serialize': 1.3.3 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.2.0 + transitivePeerDependencies: + - supports-color + + '@emotion/cache@11.14.0': + dependencies: + '@emotion/memoize': 0.9.0 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + stylis: 4.2.0 + + '@emotion/css@11.13.5': + dependencies: + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + '@emotion/utils': 1.4.2 + transitivePeerDependencies: + - supports-color + + '@emotion/hash@0.8.0': {} + + '@emotion/hash@0.9.2': {} + + '@emotion/is-prop-valid@1.3.1': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/utils': 1.4.2 + '@emotion/weak-memoize': 0.4.0 + hoist-non-react-statics: 3.3.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 19.1.8 + transitivePeerDependencies: + - supports-color + + '@emotion/serialize@1.3.3': + dependencies: + '@emotion/hash': 0.9.2 + '@emotion/memoize': 0.9.0 + '@emotion/unitless': 0.10.0 + '@emotion/utils': 1.4.2 + csstype: 3.1.3 + + '@emotion/sheet@1.4.0': {} + + '@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/babel-plugin': 11.13.5 + '@emotion/is-prop-valid': 1.3.1 + '@emotion/react': 11.14.0(@types/react@19.1.8)(react@18.3.1) + '@emotion/serialize': 1.3.3 + '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1) + '@emotion/utils': 1.4.2 + react: 18.3.1 + optionalDependencies: + '@types/react': 19.1.8 + transitivePeerDependencies: + - supports-color + + '@emotion/unitless@0.10.0': {} + + '@emotion/unitless@0.7.5': {} + + '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@emotion/utils@1.4.2': {} + + '@emotion/weak-memoize@0.4.0': {} + + '@floating-ui/core@0.7.3': {} + + '@floating-ui/core@1.7.1': + dependencies: + '@floating-ui/utils': 0.2.9 + + '@floating-ui/dom@0.5.4': + dependencies: + '@floating-ui/core': 0.7.3 + + '@floating-ui/dom@1.7.1': + dependencies: + '@floating-ui/core': 1.7.1 + '@floating-ui/utils': 0.2.9 + + '@floating-ui/react-dom@0.7.2(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 0.5.4 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + use-isomorphic-layout-effect: 1.2.1(@types/react@19.1.8)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + + '@floating-ui/react-dom@2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/dom': 1.7.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@floating-ui/react@0.27.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@floating-ui/react-dom': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@floating-ui/utils': 0.2.9 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tabbable: 6.2.0 + + '@floating-ui/utils@0.2.9': {} + + '@fontsource/inter@5.2.6': {} + + '@fontsource/jetbrains-mono@5.2.6': {} + + '@giscus/react@3.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + giscus: 1.6.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + + '@huggingface/hub@2.2.0': + dependencies: + '@huggingface/tasks': 0.19.15 + + '@huggingface/tasks@0.19.15': {} + + '@hyzyla/pdfium@2.1.7': {} + + '@iconify/types@2.0.0': {} + + '@iconify/utils@2.3.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 8.1.1 + '@iconify/types': 2.0.0 + debug: 4.4.1 + globals: 15.15.0 + kolorist: 1.8.0 + local-pkg: 1.1.1 + mlly: 1.7.4 + transitivePeerDependencies: + - supports-color + + '@img/sharp-darwin-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.0.4 + optional: true + + '@img/sharp-darwin-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.0.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.0.5': + optional: true + + '@img/sharp-libvips-linux-s390x@1.0.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.0.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.0.4': + optional: true + + '@img/sharp-linux-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.0.4 + optional: true + + '@img/sharp-linux-arm@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.0.5 + optional: true + + '@img/sharp-linux-s390x@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.0.4 + optional: true + + '@img/sharp-linux-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.33.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + optional: true + + '@img/sharp-wasm32@0.33.5': + dependencies: + '@emnapi/runtime': 1.4.3 + optional: true + + '@img/sharp-win32-ia32@0.33.5': + optional: true + + '@img/sharp-win32-x64@0.33.5': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.8': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/set-array@1.2.1': {} + + '@jridgewell/sourcemap-codec@1.5.0': {} + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@langchain/core@0.3.58(openai@4.104.0(zod@3.25.76))': + dependencies: + '@cfworker/json-schema': 4.1.1 + ansi-styles: 5.2.0 + camelcase: 6.3.0 + decamelize: 1.2.0 + js-tiktoken: 1.0.20 + langsmith: 0.3.31(openai@4.104.0(zod@3.25.76)) + mustache: 4.2.0 + p-queue: 6.6.2 + p-retry: 4.6.2 + uuid: 10.0.0 + zod: 3.25.76 + zod-to-json-schema: 3.24.5(zod@3.25.76) + transitivePeerDependencies: + - openai + + '@langchain/openai@0.5.13(@langchain/core@0.3.58(openai@4.104.0(zod@3.25.76)))': + dependencies: + '@langchain/core': 0.3.58(openai@4.104.0(zod@3.25.76)) + js-tiktoken: 1.0.20 + openai: 4.104.0(zod@3.25.32) + zod: 3.25.32 + transitivePeerDependencies: + - encoding + - ws + + '@langchain/textsplitters@0.1.0(@langchain/core@0.3.58(openai@4.104.0(zod@3.25.76)))': + dependencies: + '@langchain/core': 0.3.58(openai@4.104.0(zod@3.25.76)) + js-tiktoken: 1.0.20 + + '@lit-labs/ssr-dom-shim@1.3.0': {} + + '@lit/reactive-element@2.1.0': + dependencies: + '@lit-labs/ssr-dom-shim': 1.3.0 + + '@lobehub/emojilib@1.0.0': {} + + '@lobehub/fluent-emoji@2.0.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@lobehub/emojilib': 1.0.0 + '@lobehub/ui': 2.4.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd: 5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd-style: 3.7.1(@types/react@19.1.8)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + emoji-regex: 10.4.0 + lodash-es: 4.17.21 + lucide-react: 0.469.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-layout-kit: 1.9.1(react@18.3.1) + url-join: 5.0.0 + transitivePeerDependencies: + - '@babel/core' + - '@types/react' + - acorn + - framer-motion + - supports-color + + '@lobehub/icons@1.98.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@lobehub/ui': 2.4.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd: 5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd-style: 3.7.1(@types/react@19.1.8)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lucide-react: 0.469.0(react@18.3.1) + polished: 4.3.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-layout-kit: 1.9.1(react@18.3.1) + transitivePeerDependencies: + - '@babel/core' + - '@types/react' + - acorn + - framer-motion + - supports-color + + '@lobehub/icons@2.4.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@lobehub/ui': 2.4.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd: 5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd-style: 3.7.1(@types/react@19.1.8)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lucide-react: 0.469.0(react@18.3.1) + polished: 4.3.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-layout-kit: 1.9.1(react@18.3.1) + transitivePeerDependencies: + - '@babel/core' + - '@types/react' + - acorn + - framer-motion + - supports-color + + '@lobehub/ui@2.4.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/core': 6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) + '@dnd-kit/utilities': 3.2.2(react@18.3.1) + '@emoji-mart/data': 1.2.1 + '@emoji-mart/react': 1.1.1(emoji-mart@5.6.0)(react@18.3.1) + '@floating-ui/react': 0.27.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@giscus/react': 3.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@lobehub/fluent-emoji': 2.0.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@lobehub/icons': 2.4.0(@babel/core@7.27.4)(@types/react@19.1.8)(acorn@8.15.0)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/mdx': 3.1.0(acorn@8.15.0) + '@mdx-js/react': 3.1.0(@types/react@19.1.8)(react@18.3.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@18.3.1) + '@shikijs/transformers': 3.6.0 + '@splinetool/runtime': 0.9.526 + ahooks: 3.8.5(react@18.3.1) + antd: 5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + antd-style: 3.7.1(@types/react@19.1.8)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + chroma-js: 3.1.2 + class-variance-authority: 0.7.1 + dayjs: 1.11.13 + emoji-mart: 5.6.0 + fast-deep-equal: 3.1.3 + framer-motion: 12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + immer: 10.1.1 + katex: 0.16.22 + leva: 0.10.0(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + lodash-es: 4.17.21 + lucide-react: 0.484.0(react@18.3.1) + mermaid: 11.6.0 + numeral: 2.0.6 + polished: 4.3.1 + query-string: 9.2.0 + rc-collapse: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-footer: 0.6.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-image: 7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + re-resizable: 6.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-avatar-editor: 13.0.2(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + react-error-boundary: 5.0.0(react@18.3.1) + react-hotkeys-hook: 5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-layout-kit: 1.9.1(react@18.3.1) + react-markdown: 10.1.0(@types/react@19.1.8)(react@18.3.1) + react-merge-refs: 3.0.2(react@18.3.1) + react-rnd: 10.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-zoom-pan-pinch: 3.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rehype-katex: 7.0.1 + rehype-raw: 7.0.0 + remark-breaks: 4.0.0 + remark-gfm: 4.0.1 + remark-math: 6.0.0 + shiki: 3.6.0 + swr: 2.3.3(react@18.3.1) + ts-md5: 1.3.1 + unified: 11.0.5 + url-join: 5.0.0 + use-merge-value: 1.2.0(react@18.3.1) + uuid: 11.1.0 + transitivePeerDependencies: + - '@babel/core' + - '@types/react' + - acorn + - supports-color + + '@malept/cross-spawn-promise@1.1.1': + dependencies: + cross-spawn: 7.0.6 + + '@malept/flatpak-bundler@0.4.0': + dependencies: + debug: 4.4.1 + fs-extra: 9.1.0 + lodash: 4.17.21 + tmp-promise: 3.0.3 + transitivePeerDependencies: + - supports-color + + '@mapbox/node-pre-gyp@1.0.11': + dependencies: + detect-libc: 2.0.4 + https-proxy-agent: 5.0.1 + make-dir: 3.1.0 + node-fetch: 2.7.0 + nopt: 5.0.0 + npmlog: 5.0.1 + rimraf: 3.0.2 + semver: 7.7.2 + tar: 6.2.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + '@mdx-js/mdx@3.1.0(acorn@8.15.0)': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.0(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.0 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.4 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - acorn + - supports-color + + '@mdx-js/react@3.1.0(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.1.8 + react: 18.3.1 + + '@mermaid-js/parser@0.4.0': + dependencies: + langium: 3.3.1 + + '@mixmark-io/domino@2.2.0': {} + + '@mui/base@5.0.0-beta.40-0(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@floating-ui/react-dom': 2.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/types': 7.4.3(@types/react@19.1.8) + '@mui/utils': 5.17.1(@types/react@19.1.8)(react@18.3.1) + '@popperjs/core': 2.11.8 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 19.1.8 + + '@mui/core-downloads-tracker@5.17.1': {} + + '@mui/icons-material@5.16.14(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 19.1.8 + + '@mui/lab@5.0.0-alpha.175(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/base': 5.0.0-beta.40-0(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/material': 5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/system': 5.17.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + '@mui/types': 7.4.3(@types/react@19.1.8) + '@mui/utils': 5.17.1(@types/react@19.1.8)(react@18.3.1) + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.1.8)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + '@types/react': 19.1.8 + + '@mui/material@5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/core-downloads-tracker': 5.17.1 + '@mui/system': 5.17.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + '@mui/types': 7.4.3(@types/react@19.1.8) + '@mui/utils': 5.17.1(@types/react@19.1.8)(react@18.3.1) + '@popperjs/core': 2.11.8 + '@types/react-transition-group': 4.4.12(@types/react@19.1.8) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 19.1.0 + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.1.8)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + '@types/react': 19.1.8 + + '@mui/private-theming@5.17.1(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/utils': 5.17.1(@types/react@19.1.8)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 19.1.8 + + '@mui/styled-engine@5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/cache': 11.14.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.1.8)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + + '@mui/system@5.17.1(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/private-theming': 5.17.1(@types/react@19.1.8)(react@18.3.1) + '@mui/styled-engine': 5.16.14(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.2.24(@types/react@19.1.8) + '@mui/utils': 5.17.1(@types/react@19.1.8)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@19.1.8)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@19.1.8)(react@18.3.1))(@types/react@19.1.8)(react@18.3.1) + '@types/react': 19.1.8 + + '@mui/types@7.2.24(@types/react@19.1.8)': + optionalDependencies: + '@types/react': 19.1.8 + + '@mui/types@7.4.3(@types/react@19.1.8)': + dependencies: + '@babel/runtime': 7.27.6 + optionalDependencies: + '@types/react': 19.1.8 + + '@mui/utils@5.17.1(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@mui/types': 7.2.24(@types/react@19.1.8) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@next/env@14.2.29': {} + + '@next/swc-darwin-arm64@14.2.29': + optional: true + + '@next/swc-darwin-x64@14.2.29': + optional: true + + '@next/swc-linux-arm64-gnu@14.2.29': + optional: true + + '@next/swc-linux-arm64-musl@14.2.29': + optional: true + + '@next/swc-linux-x64-gnu@14.2.29': + optional: true + + '@next/swc-linux-x64-musl@14.2.29': + optional: true + + '@next/swc-win32-arm64-msvc@14.2.29': + optional: true + + '@next/swc-win32-ia32-msvc@14.2.29': + optional: true + + '@next/swc-win32-x64-msvc@14.2.29': + optional: true + + '@noble/hashes@1.8.0': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@opendocsg/pdf2md@0.2.1': + dependencies: + enumify: 1.0.4 + minimist: 1.2.8 + unpdf: 0.12.2 + transitivePeerDependencies: + - encoding + - supports-color + + '@openrouter/ai-sdk-provider@0.4.6(zod@3.25.76)': + dependencies: + '@ai-sdk/provider': 1.0.9 + '@ai-sdk/provider-utils': 2.1.10(zod@3.25.76) + zod: 3.25.76 + + '@opentelemetry/api@1.9.0': {} + + '@paralleldrive/cuid2@2.2.2': + dependencies: + '@noble/hashes': 1.8.0 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@popperjs/core@2.11.8': {} + + '@prisma/client@6.9.0(prisma@6.9.0(typescript@5.8.3))(typescript@5.8.3)': + optionalDependencies: + prisma: 6.9.0(typescript@5.8.3) + typescript: 5.8.3 + + '@prisma/config@6.9.0': + dependencies: + jiti: 2.4.2 + + '@prisma/debug@6.9.0': {} + + '@prisma/engines-version@6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e': {} + + '@prisma/engines@6.9.0': + dependencies: + '@prisma/debug': 6.9.0 + '@prisma/engines-version': 6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e + '@prisma/fetch-engine': 6.9.0 + '@prisma/get-platform': 6.9.0 + + '@prisma/fetch-engine@6.9.0': + dependencies: + '@prisma/debug': 6.9.0 + '@prisma/engines-version': 6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e + '@prisma/get-platform': 6.9.0 + + '@prisma/get-platform@6.9.0': + dependencies: + '@prisma/debug': 6.9.0 + + '@radix-ui/primitive@1.0.0': + dependencies: + '@babel/runtime': 7.27.6 + + '@radix-ui/react-arrow@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@radix-ui/react-compose-refs@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@18.3.1)': + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-context@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + + '@radix-ui/react-dismissable-layer@1.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + '@radix-ui/react-use-escape-keydown': 1.0.2(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@radix-ui/react-id@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 + + '@radix-ui/react-popper@1.1.1(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@floating-ui/react-dom': 0.7.2(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-arrow': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-context': 1.0.0(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + '@radix-ui/react-use-rect': 1.0.0(react@18.3.1) + '@radix-ui/react-use-size': 1.0.0(react@18.3.1) + '@radix-ui/rect': 1.0.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + + '@radix-ui/react-portal@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@radix-ui/react-presence@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@radix-ui/react-primitive@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-slot': 1.0.1(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@radix-ui/react-slot@1.0.1(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + react: 18.3.1 + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@18.3.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@18.3.1) + react: 18.3.1 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-tooltip@1.0.5(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/primitive': 1.0.0 + '@radix-ui/react-compose-refs': 1.0.0(react@18.3.1) + '@radix-ui/react-context': 1.0.0(react@18.3.1) + '@radix-ui/react-dismissable-layer': 1.0.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-id': 1.0.0(react@18.3.1) + '@radix-ui/react-popper': 1.1.1(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-portal': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-presence': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.0.1(react@18.3.1) + '@radix-ui/react-use-controllable-state': 1.0.0(react@18.3.1) + '@radix-ui/react-visually-hidden': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + + '@radix-ui/react-use-callback-ref@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + + '@radix-ui/react-use-controllable-state@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 + + '@radix-ui/react-use-escape-keydown@1.0.2(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-use-callback-ref': 1.0.0(react@18.3.1) + react: 18.3.1 + + '@radix-ui/react-use-layout-effect@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + + '@radix-ui/react-use-rect@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/rect': 1.0.0 + react: 18.3.1 + + '@radix-ui/react-use-size@1.0.0(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-use-layout-effect': 1.0.0(react@18.3.1) + react: 18.3.1 + + '@radix-ui/react-visually-hidden@1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@radix-ui/react-primitive': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@radix-ui/rect@1.0.0': + dependencies: + '@babel/runtime': 7.27.6 + + '@rc-component/async-validator@5.0.4': + dependencies: + '@babel/runtime': 7.27.6 + + '@rc-component/color-picker@2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@ant-design/fast-color': 2.0.6 + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/context@1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/mini-decimal@1.1.0': + dependencies: + '@babel/runtime': 7.27.6 + + '@rc-component/mutate-observer@1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/portal@1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/qrcode@1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/tour@1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@rc-component/trigger@2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.1.8)(react@18.3.1)(redux@5.0.1))(react@18.3.1)': + dependencies: + '@standard-schema/spec': 1.0.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.0 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 18.3.1 + react-redux: 9.2.0(@types/react@19.1.8)(react@18.3.1)(redux@5.0.1) + + '@shikijs/core@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.3 + + '@shikijs/engine-oniguruma@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + + '@shikijs/themes@3.6.0': + dependencies: + '@shikijs/types': 3.6.0 + + '@shikijs/transformers@3.6.0': + dependencies: + '@shikijs/core': 3.6.0 + '@shikijs/types': 3.6.0 + + '@shikijs/types@3.6.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + + '@sindresorhus/is@4.6.0': {} + + '@splinetool/runtime@0.9.526': + dependencies: + on-change: 4.0.2 + semver-compare: 1.0.0 + + '@standard-schema/spec@1.0.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@stitches/react@1.2.8(react@18.3.1)': + dependencies: + react: 18.3.1 + + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.5': + dependencies: + '@swc/counter': 0.1.3 + tslib: 2.8.1 + + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + + '@tootallnate/once@2.0.0': {} + + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.0.4 + '@types/keyv': 3.1.4 + '@types/node': 22.15.31 + '@types/responselike': 1.0.3 + + '@types/conventional-commits-parser@5.0.1': + dependencies: + '@types/node': 24.0.1 + + '@types/d3-array@3.2.1': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.1 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.6': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.6 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/diff-match-patch@1.0.36': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/fs-extra@9.0.13': + dependencies: + '@types/node': 24.0.1 + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/http-cache-semantics@4.0.4': {} + + '@types/katex@0.16.7': {} + + '@types/keyv@3.1.4': + dependencies: + '@types/node': 22.15.31 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/node-fetch@2.6.12': + dependencies: + '@types/node': 18.19.111 + form-data: 4.0.3 + + '@types/node@18.19.111': + dependencies: + undici-types: 5.26.5 + + '@types/node@22.15.31': + dependencies: + undici-types: 6.21.0 + + '@types/node@24.0.1': + dependencies: + undici-types: 7.8.0 + + '@types/parse-json@4.0.2': {} + + '@types/plist@3.0.5': + dependencies: + '@types/node': 24.0.1 + xmlbuilder: 15.1.1 + optional: true + + '@types/prop-types@15.7.15': {} + + '@types/react-transition-group@4.4.12(@types/react@19.1.8)': + dependencies: + '@types/react': 19.1.8 + + '@types/react@19.1.8': + dependencies: + csstype: 3.1.3 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 22.15.31 + + '@types/retry@0.12.0': {} + + '@types/trusted-types@2.0.7': {} + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/use-sync-external-store@0.0.6': {} + + '@types/uuid@10.0.0': {} + + '@types/verror@1.10.11': + optional: true + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 22.15.31 + optional: true + + '@ungap/structured-clone@1.3.0': {} + + '@use-gesture/core@10.3.1': {} + + '@use-gesture/react@10.3.1(react@18.3.1)': + dependencies: + '@use-gesture/core': 10.3.1 + react: 18.3.1 + + '@xmldom/xmldom@0.8.10': {} + + JSONStream@1.3.5: + dependencies: + jsonparse: 1.3.1 + through: 2.3.8 + + abbrev@1.1.1: + optional: true + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + adler-32@1.3.1: {} + + adm-zip@0.5.16: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + agentkeepalive@4.6.0: + dependencies: + humanize-ms: 1.2.1 + + ahooks@3.8.5(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + dayjs: 1.11.13 + intersection-observer: 0.12.2 + js-cookie: 3.0.5 + lodash: 4.17.21 + react: 18.3.1 + react-fast-compare: 3.2.2 + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + tslib: 2.8.1 + + ai@4.3.16(react@18.3.1)(zod@3.25.76): + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + '@ai-sdk/react': 1.2.12(react@18.3.1)(zod@3.25.76) + '@ai-sdk/ui-utils': 1.2.11(zod@3.25.76) + '@opentelemetry/api': 1.9.0 + jsondiffpatch: 0.6.0 + zod: 3.25.76 + optionalDependencies: + react: 18.3.1 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.0.6 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-escapes@7.0.0: + dependencies: + environment: 1.1.0 + + ansi-regex@2.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.1.0: {} + + ansi-styles@2.2.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + antd-style@3.7.1(@types/react@19.1.8)(antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.27.6 + '@emotion/cache': 11.14.0 + '@emotion/css': 11.13.5 + '@emotion/react': 11.14.0(@types/react@19.1.8)(react@18.3.1) + '@emotion/serialize': 1.3.3 + '@emotion/utils': 1.4.2 + antd: 5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + use-merge-value: 1.2.0(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - react-dom + - supports-color + + antd@5.26.0(date-fns@2.30.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@ant-design/colors': 7.2.1 + '@ant-design/cssinjs': 1.23.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/cssinjs-utils': 1.1.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/fast-color': 2.0.6 + '@ant-design/icons': 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@ant-design/react-slick': 1.1.2(react@18.3.1) + '@babel/runtime': 7.27.6 + '@rc-component/color-picker': 2.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/mutate-observer': 1.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/qrcode': 1.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/tour': 1.15.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + copy-to-clipboard: 3.3.3 + dayjs: 1.11.13 + rc-cascader: 3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-checkbox: 3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-collapse: 3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-drawer: 7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-field-form: 2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-image: 7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-input-number: 9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-mentions: 2.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-notification: 5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-pagination: 5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-picker: 4.11.3(date-fns@2.30.0)(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-progress: 4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-rate: 2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-segmented: 2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-slider: 11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-steps: 6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-switch: 4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-table: 7.51.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tabs: 15.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tooltip: 6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree-select: 5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-upload: 4.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + scroll-into-view-if-needed: 3.1.0 + throttle-debounce: 5.0.2 + transitivePeerDependencies: + - date-fns + - luxon + - moment + + app-builder-bin@4.0.0: {} + + app-builder-lib@24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3): + 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.4.1 + dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3) + ejs: 3.1.10 + electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3) + electron-publish: 24.13.1 + form-data: 4.0.3 + fs-extra: 10.1.0 + hosted-git-info: 4.1.0 + is-ci: 3.0.1 + isbinaryfile: 5.0.4 + js-yaml: 4.1.0 + lazy-val: 1.0.5 + minimatch: 5.1.6 + read-config-file: 6.3.2 + sanitize-filename: 1.6.3 + semver: 7.7.2 + tar: 6.2.1 + temp-file: 3.4.0 + transitivePeerDependencies: + - supports-color + + aproba@2.0.0: + optional: true + + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + 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.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + 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.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + + are-we-there-yet@2.0.0: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + optional: true + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-ify@1.0.0: {} + + array-union@2.1.0: {} + + asap@2.0.6: {} + + asn1@0.2.6: + dependencies: + safer-buffer: 2.1.2 + + assert-plus@0.2.0: {} + + assert-plus@1.0.0: {} + + assign-symbols@1.0.0: {} + + astral-regex@2.0.0: + optional: true + + astring@1.9.0: {} + + async-exit-hook@2.0.1: {} + + async@2.6.4: + dependencies: + lodash: 4.17.21 + + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + attr-accept@2.2.5: {} + + aws-sign2@0.6.0: {} + + aws4@1.13.2: {} + + axios@1.9.0: + dependencies: + follow-redirects: 1.15.9 + form-data: 4.0.3 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + + babel-plugin-macros@3.1.0: + dependencies: + '@babel/runtime': 7.27.6 + cosmiconfig: 7.1.0 + resolve: 1.22.10 + + babel-plugin-polyfill-corejs2@0.4.13(@babel/core@7.27.4): + dependencies: + '@babel/compat-data': 7.27.5 + '@babel/core': 7.27.4 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.27.4) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.11.1(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.27.4) + core-js-compat: 3.43.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.4(@babel/core@7.27.4): + dependencies: + '@babel/core': 7.27.4 + '@babel/helper-define-polyfill-provider': 0.6.4(@babel/core@7.27.4) + transitivePeerDependencies: + - supports-color + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bcrypt-pbkdf@1.0.2: + dependencies: + tweetnacl: 0.14.5 + + bl@1.1.2: + dependencies: + readable-stream: 2.0.6 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird-lst@1.0.9: + dependencies: + bluebird: 3.7.2 + + bluebird@3.4.7: {} + + bluebird@3.7.2: {} + + boolean@3.2.0: + optional: true + + boom@2.10.1: + dependencies: + hoek: 2.16.3 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.0: + dependencies: + caniuse-lite: 1.0.30001722 + electron-to-chromium: 1.5.166 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.0) + + buffer-crc32@0.2.13: {} + + buffer-equal@1.0.1: {} + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builder-util-runtime@9.2.4: + dependencies: + debug: 4.4.1 + sax: 1.4.1 + transitivePeerDependencies: + - supports-color + + builder-util-runtime@9.3.1: + dependencies: + debug: 4.4.1 + sax: 1.4.1 + transitivePeerDependencies: + - supports-color + + builder-util@24.13.1: + dependencies: + 7zip-bin: 5.2.0 + '@types/debug': 4.1.12 + 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.6 + debug: 4.4.1 + fs-extra: 10.1.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-ci: 3.0.1 + js-yaml: 4.1.0 + source-map-support: 0.5.21 + stat-mode: 1.0.0 + temp-file: 3.4.0 + transitivePeerDependencies: + - supports-color + + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001722: {} + + canvas@2.11.2: + dependencies: + '@mapbox/node-pre-gyp': 1.0.11 + nan: 2.22.2 + simple-get: 3.1.1 + transitivePeerDependencies: + - encoding + - supports-color + optional: true + + caseless@0.11.0: {} + + ccount@2.0.1: {} + + cfb@1.2.2: + dependencies: + adler-32: 1.3.1 + crc-32: 1.2.2 + + chalk@1.1.3: + dependencies: + ansi-styles: 2.2.1 + escape-string-regexp: 1.0.5 + has-ansi: 2.0.0 + strip-ansi: 3.0.1 + supports-color: 2.0.0 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.4.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.21 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + + chownr@1.1.4: {} + + chownr@2.0.0: {} + + chroma-js@3.1.2: {} + + chromium-pickle-js@0.2.0: {} + + ci-info@3.9.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + classnames@2.5.1: {} + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + optional: true + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.2.0 + + client-only@0.0.1: {} + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + clsx@1.2.1: {} + + clsx@2.1.1: {} + + co-prompt@1.0.0: + dependencies: + keypress: 0.2.1 + + co@4.6.0: {} + + codepage@1.15.0: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color-support@1.1.3: + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + colord@2.9.3: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + comma-separated-tokens@2.0.3: {} + + commander@13.1.0: {} + + commander@2.9.0: + dependencies: + graceful-readlink: 1.0.1 + + commander@5.1.0: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + compare-func@2.0.0: + dependencies: + array-ify: 1.0.0 + dot-prop: 5.3.0 + + compare-version@0.1.2: {} + + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + compute-scroll-into-view@3.1.1: {} + + concat-map@0.0.1: {} + + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.3 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + config-file-ts@0.2.6: + dependencies: + glob: 10.4.5 + typescript: 5.8.3 + + console-control-strings@1.1.0: + optional: true + + console-table-printer@2.14.3: + dependencies: + simple-wcswidth: 1.0.1 + + conventional-changelog-angular@7.0.0: + dependencies: + compare-func: 2.0.0 + + conventional-changelog-conventionalcommits@7.0.2: + dependencies: + compare-func: 2.0.0 + + conventional-commits-parser@5.0.0: + dependencies: + JSONStream: 1.3.5 + is-text-path: 2.0.0 + meow: 12.1.1 + split2: 4.2.0 + + convert-source-map@1.9.0: {} + + convert-source-map@2.0.0: {} + + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + + core-js-compat@3.43.0: + dependencies: + browserslist: 4.25.0 + + core-util-is@1.0.2: {} + + core-util-is@1.0.3: {} + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + + cosmiconfig-typescript-loader@6.1.0(@types/node@24.0.1)(cosmiconfig@9.0.0(typescript@5.8.3))(typescript@5.8.3): + dependencies: + '@types/node': 24.0.1 + cosmiconfig: 9.0.0(typescript@5.8.3) + jiti: 2.4.2 + typescript: 5.8.3 + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + + cosmiconfig@9.0.0(typescript@5.8.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.8.3 + + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + + crc@3.8.0: + dependencies: + buffer: 5.7.1 + optional: true + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cryptiles@2.0.5: + dependencies: + boom: 2.10.1 + + csstype@3.1.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.32.0): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.32.0 + + cytoscape-fcose@2.2.0(cytoscape@3.32.0): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.32.0 + + cytoscape@3.32.0: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.0: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.11: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + + dargs@8.1.0: {} + + dashdash@1.14.1: + dependencies: + assert-plus: 1.0.0 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.27.6 + + dayjs@1.11.13: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + decimal.js-light@2.5.1: {} + + decode-named-character-reference@1.1.0: + dependencies: + character-entities: 2.0.2 + + decode-uri-component@0.4.1: {} + + decompress-response@4.2.1: + dependencies: + mimic-response: 2.1.0 + optional: true + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-extend@0.6.0: {} + + defer-to-connect@2.0.1: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + optional: true + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + optional: true + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + delayed-stream@1.0.0: {} + + delegates@1.0.0: + optional: true + + dequal@2.0.3: {} + + detect-libc@2.0.4: {} + + detect-node@2.1.0: + optional: true + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dezalgo@1.0.4: + dependencies: + asap: 2.0.6 + wrappy: 1.0.2 + + diff-match-patch@1.0.5: {} + + dingbat-to-unicode@1.0.1: {} + + dir-compare@3.3.0: + dependencies: + buffer-equal: 1.0.1 + minimatch: 3.1.2 + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + dmg-builder@24.13.3(electron-builder-squirrel-windows@24.13.3): + dependencies: + app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) + builder-util: 24.13.1 + builder-util-runtime: 9.2.4 + fs-extra: 10.1.0 + iconv-lite: 0.6.3 + js-yaml: 4.1.0 + optionalDependencies: + dmg-license: 1.0.11 + transitivePeerDependencies: + - electron-builder-squirrel-windows + - supports-color + + dmg-license@1.0.11: + dependencies: + '@types/plist': 3.0.5 + '@types/verror': 1.10.11 + ajv: 6.12.6 + crc: 3.8.0 + iconv-corefoundation: 1.1.7 + plist: 3.1.0 + smart-buffer: 4.2.0 + verror: 1.10.1 + optional: true + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.27.6 + csstype: 3.1.3 + + dompurify@3.2.6: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + dot-prop@5.3.0: + dependencies: + is-obj: 2.0.0 + + dotenv-expand@5.1.0: {} + + dotenv@9.0.2: {} + + duck@0.1.12: + dependencies: + underscore: 1.13.7 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ecc-jsbn@0.1.2: + dependencies: + jsbn: 0.1.1 + safer-buffer: 2.1.2 + + ejs@3.1.10: + dependencies: + jake: 10.9.2 + + electron-build@0.0.3: + dependencies: + co: 4.6.0 + co-prompt: 1.0.0 + commander: 2.9.0 + jszip: 2.5.0 + path: 0.12.7 + progress: 1.1.8 + q: 1.4.1 + request: 2.72.0 + url: 0.11.0 + + electron-builder-squirrel-windows@24.13.3(dmg-builder@24.13.3): + dependencies: + app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) + archiver: 5.3.2 + builder-util: 24.13.1 + fs-extra: 10.1.0 + transitivePeerDependencies: + - dmg-builder + - supports-color + + electron-builder@24.13.3(electron-builder-squirrel-windows@24.13.3): + dependencies: + app-builder-lib: 24.13.3(dmg-builder@24.13.3)(electron-builder-squirrel-windows@24.13.3) + builder-util: 24.13.1 + builder-util-runtime: 9.2.4 + chalk: 4.1.2 + dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3) + fs-extra: 10.1.0 + is-ci: 3.0.1 + lazy-val: 1.0.5 + read-config-file: 6.3.2 + simple-update-notifier: 2.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - electron-builder-squirrel-windows + - supports-color + + electron-publish@24.13.1: + dependencies: + '@types/fs-extra': 9.0.13 + 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.6.0 + transitivePeerDependencies: + - supports-color + + electron-to-chromium@1.5.166: {} + + electron-updater@6.6.2: + dependencies: + builder-util-runtime: 9.3.1 + fs-extra: 10.1.0 + js-yaml: 4.1.0 + lazy-val: 1.0.5 + lodash.escaperegexp: 4.1.2 + lodash.isequal: 4.5.0 + semver: 7.7.2 + tiny-typed-emitter: 2.1.0 + transitivePeerDependencies: + - supports-color + + electron@35.5.1: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 22.15.31 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + + emoji-mart@5.6.0: {} + + emoji-regex@10.4.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.4: + dependencies: + once: 1.4.0 + + entities@6.0.1: {} + + enumify@1.0.4: {} + + env-paths@2.2.1: {} + + environment@1.1.0: {} + + err-code@2.0.3: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-toolkit@1.43.0: {} + + es6-error@4.1.1: + optional: true + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.2 + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.4 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + event-target-shim@5.0.1: {} + + eventemitter3@4.0.7: {} + + eventemitter3@5.0.1: {} + + eventsource-parser@3.0.2: {} + + eventsource-parser@3.0.6: {} + + execa@8.0.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 8.0.1 + human-signals: 5.0.0 + is-stream: 3.0.0 + merge-stream: 2.0.0 + npm-run-path: 5.3.0 + onetime: 6.0.0 + signal-exit: 4.1.0 + strip-final-newline: 3.0.0 + + expand-template@2.0.3: {} + + exsolve@1.0.5: {} + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend-shallow@3.0.2: + dependencies: + assign-symbols: 1.0.0 + is-extendable: 1.0.1 + + extend@3.0.2: {} + + extract-zip@2.0.1: + dependencies: + debug: 4.4.1 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + extsprintf@1.3.0: {} + + extsprintf@1.4.1: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-uri@3.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + file-selector@0.5.0: + dependencies: + tslib: 2.8.1 + + filelist@1.0.4: + dependencies: + minimatch: 5.1.6 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + filter-obj@5.1.0: {} + + find-root@1.1.0: {} + + find-up@7.0.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + unicorn-magic: 0.1.0 + + follow-redirects@1.15.9: {} + + for-in@1.0.2: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + forever-agent@0.6.1: {} + + form-data-encoder@1.7.2: {} + + form-data@1.0.1: + dependencies: + async: 2.6.4 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + form-data@4.0.3: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + formdata-node@4.4.1: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + + formidable@3.5.4: + dependencies: + '@paralleldrive/cuid2': 2.2.2 + dezalgo: 1.0.4 + once: 1.4.0 + + frac@1.1.2: {} + + framer-motion@12.17.0(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + motion-dom: 12.17.0 + motion-utils: 12.12.1 + tslib: 2.8.1 + optionalDependencies: + '@emotion/is-prop-valid': 1.3.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + from2@2.3.0: + dependencies: + inherits: 2.0.4 + readable-stream: 2.3.8 + + fs-constants@1.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@11.3.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs.realpath@1.0.0: {} + + function-bind@1.1.2: {} + + gauge@3.0.2: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + object-assign: 4.1.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + optional: true + + generate-function@2.3.1: + dependencies: + is-property: 1.0.2 + + generate-object-property@1.2.0: + dependencies: + is-property: 1.0.2 + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.3.0: {} + + get-intrinsic@1.3.0: + 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 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.2 + + get-stream@8.0.1: {} + + get-value@2.0.6: {} + + getpass@0.1.7: + dependencies: + assert-plus: 1.0.0 + + giscus@1.6.0: + dependencies: + lit: 3.3.0 + + git-raw-commits@4.0.0: + dependencies: + dargs: 8.1.0 + meow: 12.1.1 + split2: 4.2.0 + + github-from-package@0.0.0: {} + + github-markdown-css@5.8.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.2 + once: 1.4.0 + path-is-absolute: 1.0.1 + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.7.2 + serialize-error: 7.0.1 + optional: true + + global-directory@4.0.1: + dependencies: + ini: 4.1.1 + + globals@11.12.0: {} + + globals@15.15.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + optional: true + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + + graceful-fs@4.2.11: {} + + graceful-readlink@1.0.1: {} + + hachure-fill@0.5.2: {} + + har-validator@2.0.6: + dependencies: + chalk: 1.1.3 + commander: 2.9.0 + is-my-json-valid: 2.20.6 + pinkie-promise: 2.0.1 + + has-ansi@2.0.0: + dependencies: + ansi-regex: 2.1.1 + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + optional: true + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + has-unicode@2.0.1: + optional: true + + has@1.0.4: {} + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.2 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.16 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.16 + unist-util-position: 5.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.0: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + hawk@3.1.3: + dependencies: + boom: 2.10.1 + cryptiles: 2.0.5 + hoek: 2.16.3 + sntp: 1.0.9 + + hoek@2.16.3: {} + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + html-parse-stringify@3.0.1: + dependencies: + void-elements: 3.1.0 + + html-url-attributes@3.0.1: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + http-signature@1.1.1: + dependencies: + assert-plus: 0.2.0 + jsprim: 1.4.2 + sshpk: 1.18.0 + + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + human-signals@5.0.0: {} + + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + husky@9.1.7: {} + + i18next-browser-languagedetector@8.2.0: + dependencies: + '@babel/runtime': 7.27.6 + + i18next@24.2.3(typescript@5.8.3): + dependencies: + '@babel/runtime': 7.27.6 + optionalDependencies: + typescript: 5.8.3 + + iconv-corefoundation@1.1.7: + dependencies: + cli-truncate: 2.1.0 + node-addon-api: 1.7.2 + optional: true + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + image-size@2.0.2: {} + + immediate@3.0.6: {} + + immer@10.1.1: {} + + immer@11.1.0: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-meta-resolve@4.1.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.3: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + ini@4.1.1: {} + + inline-style-parser@0.2.4: {} + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + intersection-observer@0.12.2: {} + + into-stream@6.0.0: + dependencies: + from2: 2.3.0 + p-is-promise: 3.0.0 + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.2: {} + + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-core-module@2.9.0: + dependencies: + has: 1.0.4 + + is-decimal@2.0.1: {} + + is-extendable@0.1.1: {} + + is-extendable@1.0.1: + dependencies: + is-plain-object: 2.0.4 + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.3.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-my-ip-valid@1.0.1: {} + + is-my-json-valid@2.20.6: + dependencies: + generate-function: 2.3.1 + generate-object-property: 1.2.0 + is-my-ip-valid: 1.0.1 + jsonpointer: 5.0.1 + xtend: 4.0.2 + + is-number@7.0.0: {} + + is-obj@2.0.0: {} + + is-plain-obj@4.1.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-property@1.0.2: {} + + is-stream@3.0.0: {} + + is-text-path@2.0.0: + dependencies: + text-extensions: 2.4.0 + + is-typedarray@1.0.0: {} + + isarray@1.0.0: {} + + isbinaryfile@4.0.10: {} + + isbinaryfile@5.0.4: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + isstream@0.1.2: {} + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jake@10.9.2: + dependencies: + async: 3.2.6 + chalk: 4.1.2 + filelist: 1.0.4 + minimatch: 3.1.2 + + jiti@2.4.2: {} + + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + + jotai@2.12.5(@types/react@19.1.8)(react@18.3.1): + optionalDependencies: + '@types/react': 19.1.8 + react: 18.3.1 + + js-cookie@3.0.5: {} + + js-tiktoken@1.0.20: + dependencies: + base64-js: 1.5.1 + + js-tokens@4.0.0: {} + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsbn@0.1.1: {} + + jsesc@2.5.2: {} + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-schema-traverse@1.0.0: {} + + json-schema@0.4.0: {} + + json-stringify-safe@5.0.1: {} + + json2mq@0.2.0: + dependencies: + string-convert: 0.2.1 + + json5@2.2.3: {} + + jsondiffpatch@0.6.0: + dependencies: + '@types/diff-match-patch': 1.0.36 + chalk: 5.4.1 + diff-match-patch: 1.0.5 + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsonparse@1.3.1: {} + + jsonpointer@5.0.1: {} + + jsonrepair@3.13.1: {} + + jsprim@1.4.2: + dependencies: + assert-plus: 1.0.0 + extsprintf: 1.3.0 + json-schema: 0.4.0 + verror: 1.10.0 + + jszip@2.5.0: + dependencies: + pako: 0.2.9 + + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + + katex@0.16.22: + dependencies: + commander: 8.3.0 + + keypress@0.2.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + khroma@2.1.0: {} + + kolorist@1.8.0: {} + + langchain@0.3.28(@langchain/core@0.3.58(openai@4.104.0(zod@3.25.76)))(axios@1.9.0)(openai@4.104.0(zod@3.25.76)): + dependencies: + '@langchain/core': 0.3.58(openai@4.104.0(zod@3.25.76)) + '@langchain/openai': 0.5.13(@langchain/core@0.3.58(openai@4.104.0(zod@3.25.76))) + '@langchain/textsplitters': 0.1.0(@langchain/core@0.3.58(openai@4.104.0(zod@3.25.76))) + js-tiktoken: 1.0.20 + js-yaml: 4.1.0 + jsonpointer: 5.0.1 + langsmith: 0.3.31(openai@4.104.0(zod@3.25.76)) + openapi-types: 12.1.3 + p-retry: 4.6.2 + uuid: 10.0.0 + yaml: 2.8.0 + zod: 3.25.76 + optionalDependencies: + axios: 1.9.0 + transitivePeerDependencies: + - encoding + - openai + - ws + + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + + langsmith@0.3.31(openai@4.104.0(zod@3.25.76)): + dependencies: + '@types/uuid': 10.0.0 + chalk: 4.1.2 + console-table-printer: 2.14.3 + p-queue: 6.6.2 + p-retry: 4.6.2 + semver: 7.7.2 + uuid: 10.0.0 + optionalDependencies: + openai: 4.104.0(zod@3.25.76) + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + + lazy-val@1.0.5: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + + leva@0.10.0(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@radix-ui/react-portal': 1.0.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-tooltip': 1.0.5(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@stitches/react': 1.2.8(react@18.3.1) + '@use-gesture/react': 10.3.1(react@18.3.1) + colord: 2.9.3 + dequal: 2.0.3 + merge-value: 1.0.0 + react: 18.3.1 + react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-dom: 18.3.1(react@18.3.1) + react-dropzone: 12.1.0(react@18.3.1) + v8n: 1.5.1 + zustand: 3.7.2(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + lint-staged@15.5.2: + dependencies: + chalk: 5.4.1 + commander: 13.1.0 + debug: 4.4.1 + execa: 8.0.1 + lilconfig: 3.1.3 + listr2: 8.3.3 + micromatch: 4.0.8 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.8.0 + transitivePeerDependencies: + - supports-color + + listr2@8.3.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + + lit-element@4.2.0: + dependencies: + '@lit-labs/ssr-dom-shim': 1.3.0 + '@lit/reactive-element': 2.1.0 + lit-html: 3.3.0 + + lit-html@3.3.0: + dependencies: + '@types/trusted-types': 2.0.7 + + lit@3.3.0: + dependencies: + '@lit/reactive-element': 2.1.0 + lit-element: 4.2.0 + lit-html: 3.3.0 + + local-pkg@1.1.1: + dependencies: + mlly: 1.7.4 + pkg-types: 2.1.0 + quansync: 0.2.10 + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash-es@4.17.21: {} + + lodash.camelcase@4.3.0: {} + + lodash.debounce@4.0.8: {} + + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.flatten@4.4.0: {} + + lodash.isequal@4.5.0: {} + + lodash.isplainobject@4.0.6: {} + + lodash.kebabcase@4.1.1: {} + + lodash.merge@4.6.2: {} + + lodash.mergewith@4.6.2: {} + + lodash.snakecase@4.1.1: {} + + lodash.startcase@4.4.0: {} + + lodash.union@4.6.0: {} + + lodash.uniq@4.5.0: {} + + lodash.upperfirst@4.3.1: {} + + lodash@4.17.21: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.0.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lop@0.4.2: + dependencies: + duck: 0.1.12 + option: 0.2.4 + underscore: 1.13.7 + + lowercase-keys@2.0.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lucide-react@0.469.0(react@18.3.1): + dependencies: + react: 18.3.1 + + lucide-react@0.484.0(react@18.3.1): + dependencies: + react: 18.3.1 + + make-dir@3.1.0: + dependencies: + semver: 6.3.1 + optional: true + + mammoth@1.9.1: + dependencies: + '@xmldom/xmldom': 0.8.10 + argparse: 1.0.10 + base64-js: 1.5.1 + bluebird: 3.4.7 + dingbat-to-unicode: 1.0.1 + jszip: 3.10.1 + lop: 0.4.2 + path-is-absolute: 1.0.1 + underscore: 1.13.7 + xmlbuilder: 10.1.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + marked@15.0.12: {} + + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + + math-intrinsics@1.1.0: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-newline-to-break@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-find-and-replace: 3.0.2 + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + meow@12.1.1: {} + + merge-stream@2.0.0: {} + + merge-value@1.0.0: + dependencies: + get-value: 2.0.6 + is-extendable: 1.0.1 + mixin-deep: 1.3.2 + set-value: 2.0.1 + + merge2@1.4.1: {} + + mermaid@11.6.0: + dependencies: + '@braintree/sanitize-url': 7.1.1 + '@iconify/utils': 2.3.0 + '@mermaid-js/parser': 0.4.0 + '@types/d3': 7.4.3 + cytoscape: 3.32.0 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.32.0) + cytoscape-fcose: 2.2.0(cytoscape@3.32.0) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.11 + dayjs: 1.11.13 + dompurify: 3.2.6 + katex: 0.16.22 + khroma: 2.1.0 + lodash-es: 4.17.21 + marked: 15.0.12 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + transitivePeerDependencies: + - supports-color + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.7 + devlop: 1.1.0 + katex: 0.16.22 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.2 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.2 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.1 + decode-named-character-reference: 1.1.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@2.6.0: {} + + mimic-fn@4.0.0: {} + + mimic-function@5.0.1: {} + + mimic-response@1.0.1: {} + + mimic-response@2.1.0: + optional: true + + mimic-response@3.1.0: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.2: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mixin-deep@1.3.2: + dependencies: + for-in: 1.0.2 + is-extendable: 1.0.1 + + mkdirp-classic@0.5.3: {} + + mkdirp@1.0.4: {} + + mlly@1.7.4: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + motion-dom@12.17.0: + dependencies: + motion-utils: 12.12.1 + + motion-utils@12.12.1: {} + + ms@2.1.3: {} + + multistream@4.1.0: + dependencies: + once: 1.4.0 + readable-stream: 3.6.2 + + mustache@4.2.0: {} + + nan@2.22.2: + optional: true + + nanoid@3.3.11: {} + + nanoid@5.1.5: {} + + napi-build-utils@1.0.2: {} + + next-themes@0.2.1(next@14.2.29(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + next: 14.2.29(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + next@14.2.29(@babel/core@7.27.4)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 14.2.29 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001722 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.27.4)(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.29 + '@next/swc-darwin-x64': 14.2.29 + '@next/swc-linux-arm64-gnu': 14.2.29 + '@next/swc-linux-arm64-musl': 14.2.29 + '@next/swc-linux-x64-gnu': 14.2.29 + '@next/swc-linux-x64-musl': 14.2.29 + '@next/swc-win32-arm64-msvc': 14.2.29 + '@next/swc-win32-ia32-msvc': 14.2.29 + '@next/swc-win32-x64-msvc': 14.2.29 + '@opentelemetry/api': 1.9.0 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-abi@3.75.0: + dependencies: + semver: 7.7.2 + + node-addon-api@1.7.2: + optional: true + + node-domexception@1.0.0: {} + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-releases@2.0.19: {} + + node-uuid@1.4.8: {} + + nopt@5.0.0: + dependencies: + abbrev: 1.1.1 + optional: true + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + npm-run-path@5.3.0: + dependencies: + path-key: 4.0.0 + + npmlog@5.0.1: + dependencies: + are-we-there-yet: 2.0.0 + console-control-strings: 1.1.0 + gauge: 3.0.2 + set-blocking: 2.0.0 + optional: true + + numeral@2.0.6: {} + + oauth-sign@0.8.2: {} + + object-assign@4.1.1: {} + + object-keys@1.1.1: + optional: true + + ollama-ai-provider@1.2.0(zod@3.25.76): + dependencies: + '@ai-sdk/provider': 1.1.3 + '@ai-sdk/provider-utils': 2.2.8(zod@3.25.76) + partial-json: 0.1.7 + optionalDependencies: + zod: 3.25.76 + + on-change@4.0.2: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@6.0.0: + dependencies: + mimic-fn: 4.0.0 + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.3: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.0.1 + regex-recursion: 6.0.2 + + openai@4.104.0(zod@3.25.32): + dependencies: + '@types/node': 18.19.111 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + zod: 3.25.32 + transitivePeerDependencies: + - encoding + + openai@4.104.0(zod@3.25.76): + dependencies: + '@types/node': 18.19.111 + '@types/node-fetch': 2.6.12 + abort-controller: 3.0.0 + agentkeepalive: 4.6.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.7.0 + optionalDependencies: + zod: 3.25.76 + transitivePeerDependencies: + - encoding + optional: true + + openapi-types@12.1.3: {} + + opener@1.5.2: {} + + option@0.2.4: {} + + p-cancelable@2.1.1: {} + + p-finally@1.0.0: {} + + p-is-promise@3.0.0: {} + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.1 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + package-json-from-dist@1.0.1: {} + + package-manager-detector@1.3.0: {} + + pako@0.2.9: {} + + pako@1.0.11: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.1.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.27.1 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + partial-json@0.1.7: {} + + path-data-parser@0.1.0: {} + + path-exists@5.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + path-type@4.0.0: {} + + path@0.12.7: + dependencies: + process: 0.11.10 + util: 0.10.4 + + pathe@2.0.3: {} + + pdf2md-js@1.0.8: + dependencies: + '@hyzyla/pdfium': 2.1.7 + fs-extra: 11.3.0 + sharp: 0.33.5 + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + pidtree@0.6.0: {} + + pinkie-promise@2.0.1: + dependencies: + pinkie: 2.0.4 + + pinkie@2.0.4: {} + + pkg-fetch@3.4.2: + dependencies: + chalk: 4.1.2 + fs-extra: 9.1.0 + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + progress: 2.0.3 + semver: 7.7.2 + tar-fs: 2.1.3 + yargs: 16.2.0 + transitivePeerDependencies: + - encoding + - supports-color + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.7.4 + pathe: 2.0.3 + + pkg-types@2.1.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.5 + pathe: 2.0.3 + + pkg@5.8.1: + dependencies: + '@babel/generator': 7.18.2 + '@babel/parser': 7.18.4 + '@babel/types': 7.19.0 + chalk: 4.1.2 + fs-extra: 9.1.0 + globby: 11.1.0 + into-stream: 6.0.0 + is-core-module: 2.9.0 + minimist: 1.2.8 + multistream: 4.1.0 + pkg-fetch: 3.4.2 + prebuild-install: 7.1.1 + resolve: 1.22.10 + stream-meter: 1.0.4 + transitivePeerDependencies: + - encoding + - supports-color + + plist@3.1.0: + dependencies: + '@xmldom/xmldom': 0.8.10 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + polished@4.3.1: + dependencies: + '@babel/runtime': 7.27.6 + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prebuild-install@7.1.1: + dependencies: + detect-libc: 2.0.4 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.75.0 + pump: 3.0.2 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.3 + tunnel-agent: 0.6.0 + + prisma@6.9.0(typescript@5.8.3): + dependencies: + '@prisma/config': 6.9.0 + '@prisma/engines': 6.9.0 + optionalDependencies: + typescript: 5.8.3 + + process-nextick-args@1.0.7: {} + + process-nextick-args@2.0.1: {} + + process@0.11.10: {} + + progress@1.1.8: {} + + progress@2.0.3: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@6.5.0: {} + + property-information@7.1.0: {} + + proxy-from-env@1.1.0: {} + + pump@3.0.2: + dependencies: + end-of-stream: 1.4.4 + once: 1.4.0 + + punycode@1.3.2: {} + + punycode@2.3.1: {} + + q@1.4.1: {} + + qs@6.1.2: {} + + quansync@0.2.10: {} + + query-string@9.2.0: + dependencies: + decode-uri-component: 0.4.1 + filter-obj: 5.1.0 + split-on-first: 3.0.0 + + querystring@0.2.0: {} + + queue-microtask@1.2.3: {} + + quick-lru@5.1.1: {} + + rc-cascader@3.34.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-checkbox@3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-collapse@3.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-collapse@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dialog@9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-drawer@7.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-dropdown@4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-field-form@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/async-validator': 5.0.4 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-footer@0.6.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-image@7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/portal': 1.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-dialog: 9.6.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input-number@9.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/mini-decimal': 1.1.0 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-input@1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-mentions@2.20.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-textarea: 1.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-menu@9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-motion@2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-notification@5.6.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-overflow@1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-pagination@5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-picker@4.11.3(date-fns@2.30.0)(dayjs@1.11.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + date-fns: 2.30.0 + dayjs: 1.11.13 + + rc-progress@4.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-rate@2.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-resize-observer@1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + resize-observer-polyfill: 1.5.1 + + rc-segmented@2.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-select@14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-overflow: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.18.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-slider@11.1.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-steps@6.0.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-switch@4.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-table@7.51.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/context': 1.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.18.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tabs@15.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-dropdown: 4.2.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-menu: 9.16.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-textarea@1.10.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-input: 1.8.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tooltip@6.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@rc-component/trigger': 2.2.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree-select@5.27.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-select: 14.16.8(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-tree: 5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-tree@5.13.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-motion: 2.9.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-virtual-list: 3.18.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-upload@4.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc-util@5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + + rc-virtual-list@3.18.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + classnames: 2.5.1 + rc-resize-observer: 1.4.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + rc-util: 5.44.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + re-resizable@6.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-avatar-editor@13.0.2(@babel/core@7.27.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/plugin-transform-runtime': 7.27.4(@babel/core@7.27.4) + '@babel/runtime': 7.27.6 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-draggable@4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-dropzone@12.1.0(react@18.3.1): + dependencies: + attr-accept: 2.2.5 + file-selector: 0.5.0 + prop-types: 15.8.1 + react: 18.3.1 + + react-error-boundary@5.0.0(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + react: 18.3.1 + + react-fast-compare@3.2.2: {} + + react-hotkeys-hook@5.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-i18next@15.5.2(i18next@24.2.3(typescript@5.8.3))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3): + dependencies: + '@babel/runtime': 7.27.6 + html-parse-stringify: 3.0.1 + i18next: 24.2.3(typescript@5.8.3) + react: 18.3.1 + optionalDependencies: + react-dom: 18.3.1(react@18.3.1) + typescript: 5.8.3 + + react-is@16.13.1: {} + + react-is@18.3.1: {} + + react-is@19.1.0: {} + + react-layout-kit@1.9.1(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + '@emotion/css': 11.13.5 + react: 18.3.1 + transitivePeerDependencies: + - supports-color + + react-markdown@10.1.0(@types/react@19.1.8)(react@18.3.1): + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.1.8 + devlop: 1.1.0 + hast-util-to-jsx-runtime: 2.3.6 + html-url-attributes: 3.0.1 + mdast-util-to-hast: 13.2.0 + react: 18.3.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + react-merge-refs@3.0.2(react@18.3.1): + optionalDependencies: + react: 18.3.1 + + react-redux@9.2.0(@types/react@19.1.8)(react@18.3.1)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 18.3.1 + use-sync-external-store: 1.5.0(react@18.3.1) + optionalDependencies: + '@types/react': 19.1.8 + redux: 5.0.1 + + react-rnd@10.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + re-resizable: 6.11.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-draggable: 4.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tslib: 2.6.2 + + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.27.6 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react-zoom-pan-pinch@3.7.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-config-file@6.3.2: + dependencies: + config-file-ts: 0.2.6 + dotenv: 9.0.2 + dotenv-expand: 5.1.0 + js-yaml: 4.1.0 + json5: 2.2.3 + lazy-val: 1.0.5 + + readable-stream@2.0.6: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 1.0.7 + string_decoder: 0.10.31 + util-deprecate: 1.0.2 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.6 + + recharts@3.6.0(@types/react@19.1.8)(react-dom@18.3.1(react@18.3.1))(react-is@19.1.0)(react@18.3.1)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.1.8)(react@18.3.1)(redux@5.0.1))(react@18.3.1) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.43.0 + eventemitter3: 5.0.1 + immer: 10.1.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 19.1.0 + react-redux: 9.2.0(@types/react@19.1.8)(react@18.3.1)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.5.0(react@18.3.1) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.0(acorn@8.15.0): + dependencies: + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - acorn + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.7 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.22 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + remark-breaks@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-newline-to-break: 2.0.0 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.0: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + request@2.72.0: + dependencies: + aws-sign2: 0.6.0 + aws4: 1.13.2 + bl: 1.1.2 + caseless: 0.11.0 + combined-stream: 1.0.8 + extend: 3.0.2 + forever-agent: 0.6.1 + form-data: 1.0.1 + har-validator: 2.0.6 + hawk: 3.1.3 + http-signature: 1.1.1 + is-typedarray: 1.0.0 + isstream: 0.1.2 + json-stringify-safe: 5.0.1 + mime-types: 2.1.35 + node-uuid: 1.4.8 + oauth-sign: 0.8.2 + qs: 6.1.2 + stringstream: 0.0.6 + tough-cookie: 2.2.2 + tunnel-agent: 0.4.3 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + reselect@5.1.1: {} + + resize-observer-polyfill@1.5.1: {} + + resolve-alpn@1.2.1: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + retry@0.12.0: {} + + retry@0.13.1: {} + + reusify@1.1.0: {} + + rfdc@1.4.1: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + optional: true + + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + + robust-predicates@3.0.2: {} + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + rw@1.3.3: {} + + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safer-buffer@2.1.2: {} + + sanitize-filename@1.6.3: + dependencies: + truncate-utf8-bytes: 1.0.2 + + sax@1.4.1: {} + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + screenfull@5.2.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + secure-json-parse@2.7.0: {} + + semver-compare@1.0.0: {} + + semver@6.3.1: {} + + semver@7.7.2: {} + + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + + set-blocking@2.0.0: + optional: true + + set-value@2.0.1: + dependencies: + extend-shallow: 2.0.1 + is-extendable: 0.1.1 + is-plain-object: 2.0.4 + split-string: 3.1.0 + + setimmediate@1.0.5: {} + + sharp@0.33.5: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.33.5 + '@img/sharp-darwin-x64': 0.33.5 + '@img/sharp-libvips-darwin-arm64': 1.0.4 + '@img/sharp-libvips-darwin-x64': 1.0.4 + '@img/sharp-libvips-linux-arm': 1.0.5 + '@img/sharp-libvips-linux-arm64': 1.0.4 + '@img/sharp-libvips-linux-s390x': 1.0.4 + '@img/sharp-libvips-linux-x64': 1.0.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 + '@img/sharp-libvips-linuxmusl-x64': 1.0.4 + '@img/sharp-linux-arm': 0.33.5 + '@img/sharp-linux-arm64': 0.33.5 + '@img/sharp-linux-s390x': 0.33.5 + '@img/sharp-linux-x64': 0.33.5 + '@img/sharp-linuxmusl-arm64': 0.33.5 + '@img/sharp-linuxmusl-x64': 0.33.5 + '@img/sharp-wasm32': 0.33.5 + '@img/sharp-win32-ia32': 0.33.5 + '@img/sharp-win32-x64': 0.33.5 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + shiki@3.6.0: + dependencies: + '@shikijs/core': 3.6.0 + '@shikijs/engine-javascript': 3.6.0 + '@shikijs/engine-oniguruma': 3.6.0 + '@shikijs/langs': 3.6.0 + '@shikijs/themes': 3.6.0 + '@shikijs/types': 3.6.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + signal-exit@3.0.7: + optional: true + + signal-exit@4.1.0: {} + + simple-concat@1.0.1: {} + + simple-get@3.1.1: + dependencies: + decompress-response: 4.2.1 + once: 1.4.0 + simple-concat: 1.0.1 + optional: true + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.2 + + simple-wcswidth@1.0.1: {} + + slash@3.0.0: {} + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + optional: true + + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + + smart-buffer@4.2.0: + optional: true + + sntp@1.0.9: + dependencies: + hoek: 2.16.3 + + sonner@2.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + source-map@0.7.4: {} + + space-separated-tokens@2.0.2: {} + + spawn-command@0.0.2: {} + + split-on-first@3.0.0: {} + + split-string@3.1.0: + dependencies: + extend-shallow: 3.0.2 + + split2@4.2.0: {} + + sprintf-js@1.0.3: {} + + sprintf-js@1.1.3: + optional: true + + ssf@0.11.2: + dependencies: + frac: 1.1.2 + + sshpk@1.18.0: + dependencies: + asn1: 0.2.6 + assert-plus: 1.0.0 + bcrypt-pbkdf: 1.0.2 + dashdash: 1.14.1 + ecc-jsbn: 0.1.2 + getpass: 0.1.7 + jsbn: 0.1.1 + safer-buffer: 2.1.2 + tweetnacl: 0.14.5 + + stat-mode@1.0.0: {} + + stream-meter@1.0.4: + dependencies: + readable-stream: 2.3.8 + + streamsearch@1.1.0: {} + + string-argv@0.3.2: {} + + string-convert@0.2.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.4.0 + get-east-asian-width: 1.3.0 + strip-ansi: 7.1.0 + + string_decoder@0.10.31: {} + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + stringstream@0.0.6: {} + + strip-ansi@3.0.1: + dependencies: + ansi-regex: 2.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.1.0 + + strip-final-newline@3.0.0: {} + + strip-json-comments@2.0.1: {} + + style-to-js@1.1.16: + dependencies: + style-to-object: 1.0.8 + + style-to-object@1.0.8: + dependencies: + inline-style-parser: 0.2.4 + + styled-jsx@5.1.1(@babel/core@7.27.4)(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + optionalDependencies: + '@babel/core': 7.27.4 + + stylis@4.2.0: {} + + stylis@4.3.6: {} + + sumchecker@3.0.1: + dependencies: + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + supports-color@2.0.0: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + swr@2.3.3(react@18.3.1): + dependencies: + dequal: 2.0.3 + react: 18.3.1 + use-sync-external-store: 1.5.0(react@18.3.1) + + tabbable@6.2.0: {} + + tar-fs@2.1.3: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.2 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.4 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + temp-file@3.4.0: + dependencies: + async-exit-hook: 2.0.1 + fs-extra: 10.1.0 + + text-extensions@2.4.0: {} + + throttle-debounce@5.0.2: {} + + throttleit@2.1.0: {} + + through@2.3.8: {} + + tiny-invariant@1.3.3: {} + + tiny-typed-emitter@2.1.0: {} + + tinyexec@1.0.1: {} + + tmp-promise@3.0.3: + dependencies: + tmp: 0.2.3 + + tmp@0.2.3: {} + + to-fast-properties@2.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toggle-selection@1.0.6: {} + + tough-cookie@2.2.2: {} + + tr46@0.0.3: {} + + tree-kill@1.2.2: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + truncate-utf8-bytes@1.0.2: + dependencies: + utf8-byte-length: 1.0.5 + + ts-dedent@2.2.0: {} + + ts-md5@1.3.1: {} + + tslib@2.6.2: {} + + tslib@2.8.1: {} + + tunnel-agent@0.4.3: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + turndown@7.2.0: + dependencies: + '@mixmark-io/domino': 2.2.0 + + tweetnacl@0.14.5: {} + + type-fest@0.13.1: + optional: true + + typescript@5.8.3: {} + + ufo@1.6.1: {} + + underscore@1.13.7: {} + + undici-types@5.26.5: {} + + undici-types@6.21.0: {} + + undici-types@7.8.0: {} + + unicorn-magic@0.1.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unpdf@0.12.2: + optionalDependencies: + canvas: 2.11.2 + transitivePeerDependencies: + - encoding + - supports-color + + update-browserslist-db@1.1.3(browserslist@4.25.0): + dependencies: + browserslist: 4.25.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + url-join@5.0.0: {} + + url@0.11.0: + dependencies: + punycode: 1.3.2 + querystring: 0.2.0 + + use-isomorphic-layout-effect@1.2.1(@types/react@19.1.8)(react@18.3.1): + dependencies: + react: 18.3.1 + optionalDependencies: + '@types/react': 19.1.8 + + use-merge-value@1.2.0(react@18.3.1): + dependencies: + react: 18.3.1 + + use-sync-external-store@1.5.0(react@18.3.1): + dependencies: + react: 18.3.1 + + utf8-byte-length@1.0.5: {} + + util-deprecate@1.0.2: {} + + util@0.10.4: + dependencies: + inherits: 2.0.3 + + uuid@10.0.0: {} + + uuid@11.1.0: {} + + v8n@1.5.1: {} + + verror@1.10.0: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.4.1 + + verror@1.10.1: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.4.1 + optional: true + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.2 + + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + void-elements@3.1.0: {} + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + + wait-on@7.2.0: + dependencies: + axios: 1.9.0 + joi: 17.13.3 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + + web-namespaces@2.0.1: {} + + web-streams-polyfill@4.0.0-beta.3: {} + + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + optional: true + + wmf@1.0.2: {} + + word@0.3.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.2.0 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + xlsx@0.18.5: + dependencies: + adler-32: 1.3.1 + cfb: 1.2.2 + codepage: 1.15.0 + crc-32: 1.2.2 + ssf: 0.11.2 + wmf: 1.0.2 + word: 0.3.0 + + xmlbuilder@10.1.1: {} + + xmlbuilder@15.1.1: {} + + xmldom@0.6.0: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yaml@1.10.2: {} + + yaml@2.8.0: {} + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + yocto-queue@1.2.1: {} + + zhipu-ai-provider@0.1.1(zod@3.25.76): + dependencies: + '@ai-sdk/provider': 1.0.9 + '@ai-sdk/provider-utils': 2.1.10(zod@3.25.76) + zod: 3.25.76 + + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + + zod-to-json-schema@3.24.5(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.32: {} + + zod@3.25.76: {} + + zustand@3.7.2(react@18.3.1): + optionalDependencies: + react: 18.3.1 + + zwitch@2.0.4: {} diff --git a/easy-dataset-main/prisma/generate-template.js b/easy-dataset-main/prisma/generate-template.js new file mode 100644 index 0000000..573ca80 --- /dev/null +++ b/easy-dataset-main/prisma/generate-template.js @@ -0,0 +1,48 @@ +/** + * 此脚本用于生成空的模板数据库文件(template.sqlite) + * 该文件将在应用打包时被包含,并在用户首次启动应用时作为初始数据库 + */ + +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); + +const templatePath = path.join(__dirname, 'template.sqlite'); +const sqlitePath = path.join(__dirname, 'empty.db.sqlite'); + +// 如果存在旧的模板文件,先删除 +if (fs.existsSync(templatePath)) { + console.log('删除旧的模板数据库...'); + fs.unlinkSync(templatePath); +} + +// 如果存在临时数据库文件,先删除 +if (fs.existsSync(sqlitePath)) { + console.log('删除临时数据库文件...'); + fs.unlinkSync(sqlitePath); +} + +try { + console.log('设置临时数据库路径...'); + // 设置 DATABASE_URL 环境变量 + process.env.DATABASE_URL = `file:${sqlitePath}`; + + console.log('执行 prisma db push 创建新的数据库架构...'); + // 执行 prisma db push 创建数据库架构 + execSync('npx prisma db push', { stdio: 'inherit' }); + + console.log('将生成的数据库文件复制为模板...'); + // 复制生成的数据库文件为模板 + fs.copyFileSync(sqlitePath, templatePath); + + console.log(`✅ 模板数据库已成功生成: ${templatePath}`); +} catch (error) { + console.error('❌ 生成模板数据库失败:', error); + process.exit(1); +} finally { + // 清理: 删除临时数据库文件 + if (fs.existsSync(sqlitePath)) { + console.log('清理临时数据库文件...'); + fs.unlinkSync(sqlitePath); + } +} diff --git a/easy-dataset-main/prisma/schema.prisma b/easy-dataset-main/prisma/schema.prisma new file mode 100644 index 0000000..c7b77ff --- /dev/null +++ b/easy-dataset-main/prisma/schema.prisma @@ -0,0 +1,406 @@ +generator client { + provider = "prisma-client-js" + binaryTargets = ["native", "darwin-arm64", "darwin", "windows", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x", "debian-openssl-1.1.x"] +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model Projects { + id String @id @default(nanoid(12)) + name String + description String + globalPrompt String @default("") + questionPrompt String @default("") + answerPrompt String @default("") + labelPrompt String @default("") + domainTreePrompt String @default("") + cleanPrompt String @default("") + defaultModelConfigId String? + test String @default("") + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + Questions Questions[] + Datasets Datasets[] + DatasetConversations DatasetConversations[] + Chunks Chunks[] + ModelConfig ModelConfig[] + UploadFiles UploadFiles[] + Tags Tags[] + Task Task[] + GaPairs GaPairs[] + CustomPrompts CustomPrompts[] + Images Images[] + ImageDatasets ImageDatasets[] + QuestionTemplates QuestionTemplates[] + EvalDatasets EvalDatasets[] +} + +model UploadFiles { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + fileName String + fileExt String + path String + size Int + md5 String + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + GaPairs GaPairs[] +} + +model Chunks { + id String @id @default(nanoid()) + name String + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + fileId String + fileName String + content String + summary String + size Int + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + Questions Questions[] + EvalDatasets EvalDatasets[] + + @@index([projectId]) +} + +model Tags { + id String @id @default(nanoid()) + label String + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + parentId String? + parent Tags? @relation("Tags", fields: [parentId], references: [id]) + children Tags[] @relation("Tags") + + @@index([projectId, label]) + @@index([projectId, parentId]) +} + +model Questions { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + chunk Chunks @relation(fields: [chunkId], references: [id]) + chunkId String + gaPair GaPairs? @relation(fields: [gaPairId], references: [id]) + gaPairId String? // Optional: links question to the GA pair that generated it + question String + label String + answered Boolean @default(false) + imageId String? // Optional: for image-based questions + imageName String? // Optional: for image-based questions + templateId String? // Optional: links to ImageQuestionTemplates + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@index([projectId]) + @@index([imageId]) + @@index([templateId]) + @@index([projectId, label]) +} + +model Datasets { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + questionId String + question String + answer String + answerType String? @default("text") // 'text' | 'label' | 'custom_format' + chunkName String + chunkContent String + model String + questionLabel String + cot String + confirmed Boolean @default(false) + score Float @default(0) + aiEvaluation String @default("") // AI评估结论 + tags String @default("") + note String @default("") + other String @default("") // 存储其他字段的JSON字符串 + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@index([projectId]) + @@index([projectId, confirmed, createAt, id], name: "idx_export_confirmed") + @@index([projectId, createAt], name: "idx_project_createAt") +} + +model DatasetConversations { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + questionId String // 第一个问题 Id(初始问题) + question String // 第一个问题(初始问题) + chunkId String // 基于哪个文本块生成 + model String + questionLabel String + score Float @default(0) + aiEvaluation String @default("") // AI评估结论 + tags String @default("") + note String @default("") + scenario String // 对话场景(教学/咨询/讨论等) + roleA String // 角色A设定 + roleB String // 角色B设定 + turnCount Int // 实际轮数 + maxTurns Int // 设置的最大轮数 + rawMessages String // JSON存储完整对话(和 ShareGPT 格式保持完全一致) + confirmed Boolean @default(false) + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@index([projectId]) +} + +model LlmProviders { + id String @id + name String + apiUrl String + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + LlmModels LlmModels[] +} + +model LlmModels { + id String @id @default(nanoid()) + modelId String + modelName String + provider LlmProviders @relation(fields: [providerId], references: [id]) + providerId String + createAt DateTime @default(now()) + updateAt DateTime @updatedAt +} + +model ModelConfig { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + providerId String + providerName String + endpoint String + apiKey String + modelId String + modelName String + type String + temperature Float + maxTokens Int + topP Float + topK Float + status Int + createAt DateTime @default(now()) + updateAt DateTime @updatedAt +} + +model Task { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + taskType String // 任务类型: text-processing, question-generation, answer-generation, data-distillation + status Int // 任务状态: 0-处理中, 1-已完成, 2-失败, 3-已中断 + startTime DateTime @default(now()) + endTime DateTime? + completedCount Int @default(0) + totalCount Int @default(0) + modelInfo String // JSON格式存储,包含使用的模型信息 + language String @default("zh-CN") + detail String @default("") // 任务详情 + note String @default("") // 任务备注 + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@index([projectId]) +} + +model CustomPrompts { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + promptType String // 提示词类型,对应 lib/llm/prompts 下的文件名 + promptKey String // 提示词在模块中的键名,如 QUESTION_PROMPT, QUESTION_PROMPT_EN + language String // 语言: zh-CN, en + content String // 自定义的提示词内容 + isActive Boolean @default(true) // 是否启用 + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@unique([projectId, promptType, promptKey, language]) + @@index([projectId, promptType]) + @@index([projectId, language]) +} + +model GaPairs { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + uploadFile UploadFiles @relation(fields: [fileId], references: [id], onDelete: Cascade) + fileId String + pairNumber Int // 1-5, representing the 5 generated pairs + genreTitle String // Genre name/title + genreDesc String // Genre description + audienceTitle String // Audience name/title + audienceDesc String // Audience description + isActive Boolean @default(true) // Whether this pair is active for use + questions Questions[] // Questions generated by this GA pair + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@unique([fileId, pairNumber]) + @@index([projectId]) + @@index([fileId]) +} + +model Images { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + imageName String + path String // 图片存储路径 + size Int // 文件大小(字节) + width Int? // 图片宽度 + height Int? // 图片高度 + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + ImageDatasets ImageDatasets[] + + @@unique([projectId, imageName]) + @@index([projectId]) +} + +model ImageDatasets { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + image Images @relation(fields: [imageId], references: [id], onDelete: Cascade) + imageId String + imageName String + questionId String? // Optional: links to Questions table + question String + answer String // Stores all answer types: text, JSON array for labels, or custom format JSON + answerType String @default("text") // 'text' | 'label' | 'custom_format' + model String + confirmed Boolean @default(false) + score Float @default(0) + tags String @default("") + note String @default("") + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@index([projectId]) + @@index([imageId]) + @@index([questionId]) +} + +model QuestionTemplates { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + question String // Question content + sourceType String // 'image' | 'text' - data source type + answerType String // 'text' | 'label' | 'custom_format' + description String @default("") // Question description + labels String @default("") // JSON array of label options (for answerType='label') + customFormat String @default("") // Custom format definition (for answerType='custom_format') + order Int @default(0) // Display order + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@index([projectId]) + @@index([projectId, sourceType]) +} + +model LlmUsageLogs { + id String @id @default(nanoid()) + projectId String + provider String // 提供商: openai, anthropic, google 等 + model String // 模型名称 + + // 核心指标 + inputTokens Int @default(0) + outputTokens Int @default(0) + totalTokens Int @default(0) + latency Int @default(0) // 响应耗时(毫秒) + + // 状态与追踪 + status String @default("SUCCESS") // 状态: "SUCCESS", "FAILED" + errorMessage String? // 失败原因,status="FAILED" 时填写 + + // 时间维度 + createAt DateTime @default(now()) + dateString String // 格式 "YYYY-MM-DD",用于快速按天聚合 + + @@index([projectId, dateString]) + @@index([dateString]) + @@index([provider]) + @@index([model]) +} + +model EvalDatasets { + id String @id @default(nanoid()) + project Projects @relation(fields: [projectId], references: [id], onDelete: Cascade) + projectId String + + // 题目内容 + question String // 题目内容 + questionType String // 题型: true_false, single_choice, multiple_choice, short_answer, open_ended + + // 上下文信息(关联到文本块) + chunkId String? // 关联到 Chunks 表 + chunks Chunks? @relation(fields: [chunkId], references: [id]) + + // 选项(仅选择题使用) + options String @default("") // JSON数组: ["选项A", "选项B", "选项C", "选项D"] + + // 标准答案 + correctAnswer String // 标准答案 + + tags String @default("") // 标签,逗号分隔 + note String @default("") // 备注 + + // 时间戳 + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + // 关联评估结果 + EvalResults EvalResults[] + + @@index([projectId]) + @@index([projectId, questionType]) + @@index([chunkId]) +} + +model EvalResults { + id String @id @default(nanoid()) + projectId String + taskId String // 关联到 Task 表 + + // 关联评估题目 + evalDataset EvalDatasets @relation(fields: [evalDatasetId], references: [id], onDelete: Cascade) + evalDatasetId String + + // 评估结果 + modelAnswer String // 模型的回答 + score Float @default(0) // 得分 (0-1 之间) + isCorrect Boolean @default(false) // 是否正确(用于客观题) + judgeResponse String @default("") // LLM 评分的响应(用于主观题) + + // 答题详情 + duration Int @default(0) // 答题耗时(毫秒) + status Int @default(0) // 答题状态:0-成功, 1-输出不符合规范, 2-LLM调用报错 + errorMessage String @default("") // 答题报错信息 + + // 时间戳 + createAt DateTime @default(now()) + updateAt DateTime @updatedAt + + @@unique([taskId, evalDatasetId]) // 每个任务对每道题只能有一个结果 + @@index([projectId]) + @@index([taskId]) + @@index([evalDatasetId]) +} diff --git a/easy-dataset-main/prisma/sql.json b/easy-dataset-main/prisma/sql.json new file mode 100644 index 0000000..1e8a6e0 --- /dev/null +++ b/easy-dataset-main/prisma/sql.json @@ -0,0 +1,88 @@ +[ + { + "version": "1.2.5", + "sql": "ALTER TABLE Projects ADD COLUMN test VARCHAR(255) DEFAULT '';" + }, + { + "version": "1.3.3", + "sql": "CREATE TABLE IF NOT EXISTS Task (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n taskType VARCHAR(255) NOT NULL,\n status INT NOT NULL,\n startTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n endTime TIMESTAMP NULL,\n completedCount INT DEFAULT 0,\n totalCount INT DEFAULT 0,\n modelInfo TEXT NOT NULL,\n language VARCHAR(20) DEFAULT 'zh-CN',\n detail TEXT DEFAULT '',\n note TEXT DEFAULT '',\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE\n);\n\nCREATE INDEX idx_task_projectId ON Task(projectId);" + }, + { + "version": "1.3.6", + "sql": "CREATE TABLE IF NOT EXISTS GaPairs (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n fileId VARCHAR(255) NOT NULL,\n pairNumber INT NOT NULL,\n genreTitle VARCHAR(255) NOT NULL,\n genreDesc TEXT NOT NULL,\n audienceTitle VARCHAR(255) NOT NULL,\n audienceDesc TEXT NOT NULL,\n isActive BOOLEAN DEFAULT 1 NOT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE,\n FOREIGN KEY (fileId) REFERENCES UploadFiles(id) ON DELETE CASCADE,\n UNIQUE (fileId, pairNumber)\n);\n\nCREATE INDEX idx_gapairs_projectId ON GaPairs(projectId);\nCREATE INDEX idx_gapairs_fileId ON GaPairs(fileId);" + }, + { + "version": "1.3.6", + "sql": "ALTER TABLE Questions ADD COLUMN gaPairId VARCHAR(255) NULL;" + }, + { + "version": "1.3.6", + "sql": "ALTER TABLE Questions ADD FOREIGN KEY (gaPairId) REFERENCES GaPairs(id) ON DELETE SET NULL;\n\nCREATE INDEX idx_questions_gaPairId ON Questions(gaPairId);" + }, + { + "version": "1.4.0", + "sql": "ALTER TABLE Datasets ADD COLUMN score REAL DEFAULT 0 NOT NULL;\nALTER TABLE Datasets ADD COLUMN tags TEXT DEFAULT '[]' NOT NULL;\nALTER TABLE Datasets ADD COLUMN note TEXT DEFAULT '' NOT NULL;\nALTER TABLE Datasets ADD COLUMN other TEXT DEFAULT '' NOT NULL;\nALTER TABLE Projects ADD COLUMN cleanPrompt TEXT DEFAULT '' NOT NULL;" + }, + { + "version": "1.5.0", + "sql": "CREATE TABLE IF NOT EXISTS CustomPrompts (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n promptType VARCHAR(255) NOT NULL,\n promptKey VARCHAR(255) NOT NULL,\n language VARCHAR(10) NOT NULL,\n content TEXT NOT NULL,\n isActive BOOLEAN DEFAULT 1 NOT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE,\n UNIQUE (projectId, promptType, promptKey, language)\n);\n\nCREATE INDEX idx_customprompts_projectId ON CustomPrompts(projectId);\nCREATE INDEX idx_customprompts_project_type ON CustomPrompts(projectId, promptType);\nCREATE INDEX idx_customprompts_project_language ON CustomPrompts(projectId, language);" + }, + { + "version": "1.5.0", + "sql": "ALTER TABLE Datasets ADD COLUMN aiEvaluation TEXT DEFAULT '' NOT NULL;" + }, + { + "version": "1.5.0", + "sql": "CREATE TABLE IF NOT EXISTS DatasetConversations (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n questionId VARCHAR(255) NOT NULL,\n question TEXT NOT NULL,\n chunkId VARCHAR(255) NOT NULL,\n model VARCHAR(255) NOT NULL,\n questionLabel VARCHAR(255) NOT NULL,\n score REAL DEFAULT 0 NOT NULL,\n aiEvaluation TEXT DEFAULT '' NOT NULL,\n tags TEXT DEFAULT '' NOT NULL,\n note TEXT DEFAULT '' NOT NULL,\n scenario TEXT NOT NULL,\n roleA TEXT NOT NULL,\n roleB TEXT NOT NULL,\n turnCount INT NOT NULL,\n maxTurns INT NOT NULL,\n rawMessages TEXT NOT NULL,\n confirmed BOOLEAN DEFAULT 0 NOT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE\n);\n\nCREATE INDEX idx_datasetconversations_projectId ON DatasetConversations(projectId);" + }, + { + "version": "1.6.0", + "description": "为 Questions 表添加图片相关字段", + "sql": "ALTER TABLE Questions ADD COLUMN imageId VARCHAR(255) NULL;\nALTER TABLE Questions ADD COLUMN imageName VARCHAR(255) NULL;\nALTER TABLE Questions ADD COLUMN templateId VARCHAR(255) NULL;" + }, + { + "version": "1.6.0", + "description": "为 Questions 表添加图片相关索引", + "sql": "CREATE INDEX idx_questions_imageId ON Questions(imageId);\nCREATE INDEX idx_questions_templateId ON Questions(templateId);" + }, + { + "version": "1.6.0", + "description": "为 Datasets 表添加 answerType 字段", + "sql": "ALTER TABLE Datasets ADD COLUMN answerType VARCHAR(50) DEFAULT 'text';" + }, + { + "version": "1.6.0", + "description": "创建 Images 表", + "sql": "CREATE TABLE IF NOT EXISTS Images (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n imageName VARCHAR(255) NOT NULL,\n path TEXT NOT NULL,\n size INT NOT NULL,\n width INT NULL,\n height INT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE,\n UNIQUE (projectId, imageName)\n);\n\nCREATE INDEX idx_images_projectId ON Images(projectId);" + }, + { + "version": "1.6.0", + "description": "创建 ImageDatasets 表", + "sql": "CREATE TABLE IF NOT EXISTS ImageDatasets (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n imageId VARCHAR(255) NOT NULL,\n imageName VARCHAR(255) NOT NULL,\n questionId VARCHAR(255) NULL,\n question TEXT NOT NULL,\n answer TEXT NOT NULL,\n answerType VARCHAR(50) DEFAULT 'text',\n model VARCHAR(255) NOT NULL,\n confirmed BOOLEAN DEFAULT 0 NOT NULL,\n score REAL DEFAULT 0 NOT NULL,\n tags TEXT DEFAULT '' NOT NULL,\n note TEXT DEFAULT '' NOT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE,\n FOREIGN KEY (imageId) REFERENCES Images(id) ON DELETE CASCADE\n);\n\nCREATE INDEX idx_imagedatasets_projectId ON ImageDatasets(projectId);\nCREATE INDEX idx_imagedatasets_imageId ON ImageDatasets(imageId);\nCREATE INDEX idx_imagedatasets_questionId ON ImageDatasets(questionId);" + }, + { + "version": "1.6.0", + "description": "创建 QuestionTemplates 表", + "sql": "CREATE TABLE IF NOT EXISTS QuestionTemplates (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n question TEXT NOT NULL,\n sourceType VARCHAR(50) NOT NULL,\n answerType VARCHAR(50) NOT NULL,\n description TEXT DEFAULT '' NOT NULL,\n labels TEXT DEFAULT '' NOT NULL,\n customFormat TEXT DEFAULT '' NOT NULL,\n \"order\" INT DEFAULT 0 NOT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE\n);\n\nCREATE INDEX idx_questiontemplates_projectId ON QuestionTemplates(projectId);\nCREATE INDEX idx_questiontemplates_project_source ON QuestionTemplates(projectId, sourceType);" + }, + { + "version": "1.6.2", + "description": "创建 LlmUsageLogs 表 - LLM 调用统计日志", + "sql": "CREATE TABLE IF NOT EXISTS LlmUsageLogs (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n provider VARCHAR(255) NOT NULL,\n model VARCHAR(255) NOT NULL,\n inputTokens INT DEFAULT 0 NOT NULL,\n outputTokens INT DEFAULT 0 NOT NULL,\n totalTokens INT DEFAULT 0 NOT NULL,\n latency INT DEFAULT 0 NOT NULL,\n status VARCHAR(50) DEFAULT 'SUCCESS' NOT NULL,\n errorMessage TEXT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n dateString VARCHAR(10) NOT NULL,\n PRIMARY KEY (id)\n);\n\nCREATE INDEX idx_llmusagelogs_project_date ON LlmUsageLogs(projectId, dateString);\nCREATE INDEX idx_llmusagelogs_dateString ON LlmUsageLogs(dateString);\nCREATE INDEX idx_llmusagelogs_provider ON LlmUsageLogs(provider);\nCREATE INDEX idx_llmusagelogs_model ON LlmUsageLogs(model);" + }, + { + "version": "1.6.2", + "description": "为 Tags 和 Questions 表添加索引", + "sql": "CREATE INDEX idx_tags_project_label ON Tags(projectId, label);\nCREATE INDEX idx_tags_project_parentId ON Tags(projectId, parentId);\nCREATE INDEX idx_questions_project_label ON Questions(projectId, label);" + }, + { + "version": "1.7.0", + "description": "创建 EvalDatasets 表", + "sql": "CREATE TABLE IF NOT EXISTS EvalDatasets (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n question TEXT NOT NULL,\n questionType VARCHAR(50) NOT NULL,\n chunkId VARCHAR(255) NULL,\n options TEXT DEFAULT '' NOT NULL,\n correctAnswer TEXT NOT NULL,\n tags TEXT DEFAULT '' NOT NULL,\n note TEXT DEFAULT '' NOT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE,\n FOREIGN KEY (chunkId) REFERENCES Chunks(id) ON DELETE SET NULL\n);\n\nCREATE INDEX idx_evaldatasets_projectId ON EvalDatasets(projectId);\nCREATE INDEX idx_evaldatasets_project_type ON EvalDatasets(projectId, questionType);\nCREATE INDEX idx_evaldatasets_chunkId ON EvalDatasets(chunkId);" + }, + { + "version": "1.7.0", + "description": "创建 EvalResults 表", + "sql": "CREATE TABLE IF NOT EXISTS EvalResults (\n id VARCHAR(255) NOT NULL,\n projectId VARCHAR(255) NOT NULL,\n taskId VARCHAR(255) NOT NULL,\n evalDatasetId VARCHAR(255) NOT NULL,\n modelAnswer TEXT NOT NULL,\n score REAL DEFAULT 0 NOT NULL,\n isCorrect BOOLEAN DEFAULT 0 NOT NULL,\n judgeResponse TEXT DEFAULT '' NOT NULL,\n duration INT DEFAULT 0 NOT NULL,\n status INT DEFAULT 0 NOT NULL,\n errorMessage TEXT DEFAULT '' NOT NULL,\n createAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n updateAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (id),\n FOREIGN KEY (projectId) REFERENCES Projects(id) ON DELETE CASCADE,\n FOREIGN KEY (evalDatasetId) REFERENCES EvalDatasets(id) ON DELETE CASCADE,\n UNIQUE (taskId, evalDatasetId)\n);\n\nCREATE INDEX idx_evalresults_projectId ON EvalResults(projectId);\nCREATE INDEX idx_evalresults_taskId ON EvalResults(taskId);\nCREATE INDEX idx_evalresults_evalDatasetId ON EvalResults(evalDatasetId);" + } +] diff --git a/easy-dataset-main/public/imgs/1.png b/easy-dataset-main/public/imgs/1.png new file mode 100644 index 0000000..23e42fd Binary files /dev/null and b/easy-dataset-main/public/imgs/1.png differ diff --git a/easy-dataset-main/public/imgs/10.png b/easy-dataset-main/public/imgs/10.png new file mode 100644 index 0000000..7bdd029 Binary files /dev/null and b/easy-dataset-main/public/imgs/10.png differ diff --git a/easy-dataset-main/public/imgs/2.png b/easy-dataset-main/public/imgs/2.png new file mode 100644 index 0000000..188ff56 Binary files /dev/null and b/easy-dataset-main/public/imgs/2.png differ diff --git a/easy-dataset-main/public/imgs/3.png b/easy-dataset-main/public/imgs/3.png new file mode 100644 index 0000000..a81f8b4 Binary files /dev/null and b/easy-dataset-main/public/imgs/3.png differ diff --git a/easy-dataset-main/public/imgs/4.png b/easy-dataset-main/public/imgs/4.png new file mode 100644 index 0000000..ae5aabd Binary files /dev/null and b/easy-dataset-main/public/imgs/4.png differ diff --git a/easy-dataset-main/public/imgs/5.png b/easy-dataset-main/public/imgs/5.png new file mode 100644 index 0000000..0fb784e Binary files /dev/null and b/easy-dataset-main/public/imgs/5.png differ diff --git a/easy-dataset-main/public/imgs/6.png b/easy-dataset-main/public/imgs/6.png new file mode 100644 index 0000000..d760d8c Binary files /dev/null and b/easy-dataset-main/public/imgs/6.png differ diff --git a/easy-dataset-main/public/imgs/7.png b/easy-dataset-main/public/imgs/7.png new file mode 100644 index 0000000..1733b06 Binary files /dev/null and b/easy-dataset-main/public/imgs/7.png differ diff --git a/easy-dataset-main/public/imgs/8.png b/easy-dataset-main/public/imgs/8.png new file mode 100644 index 0000000..e0805b2 Binary files /dev/null and b/easy-dataset-main/public/imgs/8.png differ diff --git a/easy-dataset-main/public/imgs/9.png b/easy-dataset-main/public/imgs/9.png new file mode 100644 index 0000000..c38ccaa Binary files /dev/null and b/easy-dataset-main/public/imgs/9.png differ diff --git a/easy-dataset-main/public/imgs/arc3.png b/easy-dataset-main/public/imgs/arc3.png new file mode 100644 index 0000000..c6adef5 Binary files /dev/null and b/easy-dataset-main/public/imgs/arc3.png differ diff --git a/easy-dataset-main/public/imgs/aw.jpg b/easy-dataset-main/public/imgs/aw.jpg new file mode 100644 index 0000000..5d1f657 Binary files /dev/null and b/easy-dataset-main/public/imgs/aw.jpg differ diff --git a/easy-dataset-main/public/imgs/aws.png b/easy-dataset-main/public/imgs/aws.png new file mode 100644 index 0000000..cc2b77a Binary files /dev/null and b/easy-dataset-main/public/imgs/aws.png differ diff --git a/easy-dataset-main/public/imgs/bg.png b/easy-dataset-main/public/imgs/bg.png new file mode 100644 index 0000000..c2e0e77 Binary files /dev/null and b/easy-dataset-main/public/imgs/bg.png differ diff --git a/easy-dataset-main/public/imgs/bg2.png b/easy-dataset-main/public/imgs/bg2.png new file mode 100644 index 0000000..f67574b Binary files /dev/null and b/easy-dataset-main/public/imgs/bg2.png differ diff --git a/easy-dataset-main/public/imgs/cn-arc.png b/easy-dataset-main/public/imgs/cn-arc.png new file mode 100644 index 0000000..5c16ad3 Binary files /dev/null and b/easy-dataset-main/public/imgs/cn-arc.png differ diff --git a/easy-dataset-main/public/imgs/default-dataset.png b/easy-dataset-main/public/imgs/default-dataset.png new file mode 100644 index 0000000..8b30328 Binary files /dev/null and b/easy-dataset-main/public/imgs/default-dataset.png differ diff --git a/easy-dataset-main/public/imgs/en-arc.png b/easy-dataset-main/public/imgs/en-arc.png new file mode 100644 index 0000000..dbb46b9 Binary files /dev/null and b/easy-dataset-main/public/imgs/en-arc.png differ diff --git a/easy-dataset-main/public/imgs/garden.jpg b/easy-dataset-main/public/imgs/garden.jpg new file mode 100644 index 0000000..d582b47 Binary files /dev/null and b/easy-dataset-main/public/imgs/garden.jpg differ diff --git a/easy-dataset-main/public/imgs/github.png b/easy-dataset-main/public/imgs/github.png new file mode 100644 index 0000000..23a43b0 Binary files /dev/null and b/easy-dataset-main/public/imgs/github.png differ diff --git a/easy-dataset-main/public/imgs/google.png b/easy-dataset-main/public/imgs/google.png new file mode 100644 index 0000000..d26ab98 Binary files /dev/null and b/easy-dataset-main/public/imgs/google.png differ diff --git a/easy-dataset-main/public/imgs/huggingface.png b/easy-dataset-main/public/imgs/huggingface.png new file mode 100644 index 0000000..2b68b0a Binary files /dev/null and b/easy-dataset-main/public/imgs/huggingface.png differ diff --git a/easy-dataset-main/public/imgs/kaggle.png b/easy-dataset-main/public/imgs/kaggle.png new file mode 100644 index 0000000..5671d0e Binary files /dev/null and b/easy-dataset-main/public/imgs/kaggle.png differ diff --git a/easy-dataset-main/public/imgs/linux.png b/easy-dataset-main/public/imgs/linux.png new file mode 100644 index 0000000..754d978 Binary files /dev/null and b/easy-dataset-main/public/imgs/linux.png differ diff --git a/easy-dataset-main/public/imgs/lluga.png b/easy-dataset-main/public/imgs/lluga.png new file mode 100644 index 0000000..764b20f Binary files /dev/null and b/easy-dataset-main/public/imgs/lluga.png differ diff --git a/easy-dataset-main/public/imgs/logo.icns b/easy-dataset-main/public/imgs/logo.icns new file mode 100644 index 0000000..f466fd2 Binary files /dev/null and b/easy-dataset-main/public/imgs/logo.icns differ diff --git a/easy-dataset-main/public/imgs/logo.ico b/easy-dataset-main/public/imgs/logo.ico new file mode 100644 index 0000000..90062f6 Binary files /dev/null and b/easy-dataset-main/public/imgs/logo.ico differ diff --git a/easy-dataset-main/public/imgs/logo.png b/easy-dataset-main/public/imgs/logo.png new file mode 100644 index 0000000..bc21700 Binary files /dev/null and b/easy-dataset-main/public/imgs/logo.png differ diff --git a/easy-dataset-main/public/imgs/logo.svg b/easy-dataset-main/public/imgs/logo.svg new file mode 100644 index 0000000..8210a38 --- /dev/null +++ b/easy-dataset-main/public/imgs/logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/logo_old.icns b/easy-dataset-main/public/imgs/logo_old.icns new file mode 100644 index 0000000..0a57cf0 Binary files /dev/null and b/easy-dataset-main/public/imgs/logo_old.icns differ diff --git a/easy-dataset-main/public/imgs/mac.png b/easy-dataset-main/public/imgs/mac.png new file mode 100644 index 0000000..31fa44e Binary files /dev/null and b/easy-dataset-main/public/imgs/mac.png differ diff --git a/easy-dataset-main/public/imgs/models/chatglm.svg b/easy-dataset-main/public/imgs/models/chatglm.svg new file mode 100644 index 0000000..a709e4a --- /dev/null +++ b/easy-dataset-main/public/imgs/models/chatglm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/claude.svg b/easy-dataset-main/public/imgs/models/claude.svg new file mode 100644 index 0000000..85abb89 --- /dev/null +++ b/easy-dataset-main/public/imgs/models/claude.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/deepseek.svg b/easy-dataset-main/public/imgs/models/deepseek.svg new file mode 100644 index 0000000..73939fe --- /dev/null +++ b/easy-dataset-main/public/imgs/models/deepseek.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/default.svg b/easy-dataset-main/public/imgs/models/default.svg new file mode 100644 index 0000000..84ad8cf --- /dev/null +++ b/easy-dataset-main/public/imgs/models/default.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/doubao.svg b/easy-dataset-main/public/imgs/models/doubao.svg new file mode 100644 index 0000000..aebe428 --- /dev/null +++ b/easy-dataset-main/public/imgs/models/doubao.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/gemini.svg b/easy-dataset-main/public/imgs/models/gemini.svg new file mode 100644 index 0000000..3a52a9f --- /dev/null +++ b/easy-dataset-main/public/imgs/models/gemini.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/glm.svg b/easy-dataset-main/public/imgs/models/glm.svg new file mode 100644 index 0000000..a709e4a --- /dev/null +++ b/easy-dataset-main/public/imgs/models/glm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/gpt.svg b/easy-dataset-main/public/imgs/models/gpt.svg new file mode 100644 index 0000000..5cd5d03 --- /dev/null +++ b/easy-dataset-main/public/imgs/models/gpt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/hunyuan.svg b/easy-dataset-main/public/imgs/models/hunyuan.svg new file mode 100644 index 0000000..b2f72ef --- /dev/null +++ b/easy-dataset-main/public/imgs/models/hunyuan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/llama.svg b/easy-dataset-main/public/imgs/models/llama.svg new file mode 100644 index 0000000..9c4ea18 --- /dev/null +++ b/easy-dataset-main/public/imgs/models/llama.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/qwen.svg b/easy-dataset-main/public/imgs/models/qwen.svg new file mode 100644 index 0000000..74aa96e --- /dev/null +++ b/easy-dataset-main/public/imgs/models/qwen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/wenxin.svg b/easy-dataset-main/public/imgs/models/wenxin.svg new file mode 100644 index 0000000..f586c65 --- /dev/null +++ b/easy-dataset-main/public/imgs/models/wenxin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/models/yi.svg b/easy-dataset-main/public/imgs/models/yi.svg new file mode 100644 index 0000000..83a0369 --- /dev/null +++ b/easy-dataset-main/public/imgs/models/yi.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/modelscope.png b/easy-dataset-main/public/imgs/modelscope.png new file mode 100644 index 0000000..99b8a78 Binary files /dev/null and b/easy-dataset-main/public/imgs/modelscope.png differ diff --git a/easy-dataset-main/public/imgs/opendatalab.png b/easy-dataset-main/public/imgs/opendatalab.png new file mode 100644 index 0000000..c9d18b3 Binary files /dev/null and b/easy-dataset-main/public/imgs/opendatalab.png differ diff --git a/easy-dataset-main/public/imgs/providers/302ai.ico b/easy-dataset-main/public/imgs/providers/302ai.ico new file mode 100644 index 0000000..11dd4b1 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/302ai.ico differ diff --git a/easy-dataset-main/public/imgs/providers/alibailian.ico b/easy-dataset-main/public/imgs/providers/alibailian.ico new file mode 100644 index 0000000..a566910 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/alibailian.ico differ diff --git a/easy-dataset-main/public/imgs/providers/deepseek.ico b/easy-dataset-main/public/imgs/providers/deepseek.ico new file mode 100644 index 0000000..8e78c0b Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/deepseek.ico differ diff --git a/easy-dataset-main/public/imgs/providers/grok.svg b/easy-dataset-main/public/imgs/providers/grok.svg new file mode 100644 index 0000000..449fc9f --- /dev/null +++ b/easy-dataset-main/public/imgs/providers/grok.svg @@ -0,0 +1 @@ +X \ No newline at end of file diff --git a/easy-dataset-main/public/imgs/providers/groq.ico b/easy-dataset-main/public/imgs/providers/groq.ico new file mode 100644 index 0000000..1ca3725 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/groq.ico differ diff --git a/easy-dataset-main/public/imgs/providers/ollama.png b/easy-dataset-main/public/imgs/providers/ollama.png new file mode 100644 index 0000000..8cd2cf1 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/ollama.png differ diff --git a/easy-dataset-main/public/imgs/providers/openai.png b/easy-dataset-main/public/imgs/providers/openai.png new file mode 100644 index 0000000..a09a1d2 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/openai.png differ diff --git a/easy-dataset-main/public/imgs/providers/openrouter.ico b/easy-dataset-main/public/imgs/providers/openrouter.ico new file mode 100644 index 0000000..5d47b23 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/openrouter.ico differ diff --git a/easy-dataset-main/public/imgs/providers/siliconflow.ico b/easy-dataset-main/public/imgs/providers/siliconflow.ico new file mode 100644 index 0000000..a4fd985 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/siliconflow.ico differ diff --git a/easy-dataset-main/public/imgs/providers/volcengine.png b/easy-dataset-main/public/imgs/providers/volcengine.png new file mode 100644 index 0000000..87017b5 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/volcengine.png differ diff --git a/easy-dataset-main/public/imgs/providers/zhipu.png b/easy-dataset-main/public/imgs/providers/zhipu.png new file mode 100644 index 0000000..0cc7d36 Binary files /dev/null and b/easy-dataset-main/public/imgs/providers/zhipu.png differ diff --git a/easy-dataset-main/public/imgs/weichat.jpg b/easy-dataset-main/public/imgs/weichat.jpg new file mode 100644 index 0000000..e6d83d0 Binary files /dev/null and b/easy-dataset-main/public/imgs/weichat.jpg differ diff --git a/easy-dataset-main/public/imgs/windows.png b/easy-dataset-main/public/imgs/windows.png new file mode 100644 index 0000000..016359c Binary files /dev/null and b/easy-dataset-main/public/imgs/windows.png differ diff --git a/easy-dataset-main/styles/blindTest.js b/easy-dataset-main/styles/blindTest.js new file mode 100644 index 0000000..541311d --- /dev/null +++ b/easy-dataset-main/styles/blindTest.js @@ -0,0 +1,169 @@ +import { alpha } from '@mui/material/styles'; + +export const blindTestStyles = theme => ({ + // 容器 + container: { + p: 3, + height: 'calc(100vh - 64px)', + display: 'flex', + flexDirection: 'column', + overflow: 'hidden', + bgcolor: 'background.default' + }, + + // 头部 + header: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + mb: 2 + }, + headerTitle: { + display: 'flex', + alignItems: 'center', + gap: 1.5 + }, + + // 进度和问题区域 + questionPaper: { + p: 3, + borderRadius: 3, + border: `1px solid ${theme.palette.divider}`, + boxShadow: 'none', + bgcolor: theme.palette.background.paper + }, + + // 回答区域容器 + answersContainer: { + display: 'flex', + gap: 3, + flex: 1, + minHeight: 0, // 关键:允许 flex 子项收缩 + mt: 2 + }, + + // 单个回答卡片 + answerPaper: { + width: 'calc(50% - 12px)', + display: 'flex', + flexDirection: 'column', + height: '100%', + borderRadius: 3, + border: `1px solid ${theme.palette.divider}`, + boxShadow: 'none', + overflow: 'hidden', + bgcolor: theme.palette.background.paper + }, + answerHeader: { + p: 2, + borderBottom: `1px solid ${theme.palette.divider}`, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + bgcolor: theme.palette.background.default + }, + answerContent: { + flex: 1, + overflow: 'auto', + p: 3, + // 增加滚动条美化 + '&::-webkit-scrollbar': { + width: '8px' + }, + '&::-webkit-scrollbar-track': { + background: 'transparent' + }, + '&::-webkit-scrollbar-thumb': { + backgroundColor: theme.palette.divider, + borderRadius: '4px' + }, + '&::-webkit-scrollbar-thumb:hover': { + backgroundColor: theme.palette.text.disabled + } + }, + answerFooter: { + p: 1.5, + borderTop: `1px solid ${theme.palette.divider}`, + bgcolor: theme.palette.background.default, + display: 'flex', + justifyContent: 'flex-end' + }, + + // 底部投票栏 + voteBar: { + p: 1.5, + borderRadius: 4, + mx: 'auto', + width: 'fit-content', + minWidth: 800, + mt: 'auto', + bgcolor: alpha(theme.palette.background.paper, 0.8), + backdropFilter: 'blur(20px)', + border: `1px solid ${theme.palette.divider}`, + boxShadow: theme.shadows[8] + }, + voteButtons: { + display: 'flex', + justifyContent: 'center', + gap: 2 + }, + voteBtn: { + flex: 1, + py: 1.2, + borderRadius: 3, + fontWeight: 600, + textTransform: 'none', + boxShadow: 'none', + '&:hover': { + boxShadow: theme.shadows[4] + } + }, + + // 结果页 + resultContainer: { + height: 'calc(100vh - 64px)', + overflow: 'auto', + p: 3 + }, + resultContent: { + maxWidth: 1200, + mx: 'auto' + }, + + // 结果卡片 + scoreCard: { + flex: 1, + borderRadius: 3, + border: `1px solid ${theme.palette.divider}`, + boxShadow: 'none', + transition: 'all 0.3s ease', + '&:hover': { + transform: 'translateY(-4px)', + boxShadow: theme.shadows[4] + } + }, + scoreCardContent: { + textAlign: 'center', + py: 5 + }, + + // 详细结果列表项 + resultItem: { + mb: 2, + borderRadius: 3, + border: `1px solid ${theme.palette.divider}`, + boxShadow: 'none', + overflow: 'hidden', + transition: 'all 0.2s ease' + }, + resultItemHeader: { + p: 2.5, + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + cursor: 'pointer', + '&:hover': { + bgcolor: theme.palette.action.hover + } + } +}); diff --git a/easy-dataset-main/styles/globals.css b/easy-dataset-main/styles/globals.css new file mode 100644 index 0000000..3dca26b --- /dev/null +++ b/easy-dataset-main/styles/globals.css @@ -0,0 +1,19 @@ +/* 添加流式输出的闪烁光标动画 */ +@keyframes blink { + 0% { + opacity: 1; + } + 50% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.blinking-cursor { + animation: blink 1s infinite; + display: inline-block; + font-weight: bold; + color: #666; +} diff --git a/easy-dataset-main/styles/home.js b/easy-dataset-main/styles/home.js new file mode 100644 index 0000000..f14b094 --- /dev/null +++ b/easy-dataset-main/styles/home.js @@ -0,0 +1,165 @@ +// styles/home.js +import { alpha } from '@mui/material/styles'; + +export const styles = { + heroSection: { + pt: { xs: 6, md: 10 }, + pb: { xs: 6, md: 8 }, + position: 'relative', + overflow: 'hidden', + transition: 'all 0.3s ease-in-out' + }, + heroBackground: theme => ({ + background: + theme.palette.mode === 'dark' + ? 'linear-gradient(135deg, rgba(42, 92, 170, 0.25) 0%, rgba(139, 92, 246, 0.25) 100%)' + : 'linear-gradient(135deg, rgba(42, 92, 170, 0.08) 0%, rgba(139, 92, 246, 0.08) 100%)', + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: 'url("/imgs/grid-pattern.png") repeat', + opacity: theme.palette.mode === 'dark' ? 0.05 : 0.03, + zIndex: 0 + } + }), + decorativeCircle: { + position: 'absolute', + width: '800px', + height: '800px', + borderRadius: '50%', + background: 'radial-gradient(circle, rgba(139, 92, 246, 0.15) 0%, rgba(42, 92, 170, 0) 70%)', + top: '-300px', + right: '-200px', + zIndex: 0, + animation: 'pulse 15s infinite ease-in-out', + '@keyframes pulse': { + '0%': { transform: 'scale(1)' }, + '50%': { transform: 'scale(1.05)' }, + '100%': { transform: 'scale(1)' } + } + }, + decorativeCircleSecond: { + position: 'absolute', + width: '500px', + height: '500px', + borderRadius: '50%', + background: 'radial-gradient(circle, rgba(42, 92, 170, 0.1) 0%, rgba(139, 92, 246, 0) 70%)', + bottom: '-200px', + left: '-100px', + zIndex: 0, + animation: 'pulse2 20s infinite ease-in-out', + '@keyframes pulse2': { + '0%': { transform: 'scale(1)' }, + '50%': { transform: 'scale(1.08)' }, + '100%': { transform: 'scale(1)' } + } + }, + gradientTitle: theme => ({ + mb: 2, + background: theme.palette.gradient.primary, + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + backgroundClip: 'text', + textFillColor: 'transparent' + }), + createButton: theme => ({ + mt: 3, + px: 4, + py: 1.2, + borderRadius: '12px', + fontSize: '1rem', + background: theme.palette.gradient.primary, + '&:hover': { + boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)' + } + }), + statsCard: theme => ({ + mt: 6, + p: { xs: 2, md: 4 }, + borderRadius: '16px', + boxShadow: theme.palette.mode === 'dark' ? '0 8px 24px rgba(0, 0, 0, 0.2)' : '0 8px 24px rgba(0, 0, 0, 0.05)', + background: theme.palette.mode === 'dark' ? 'rgba(30, 30, 30, 0.6)' : 'rgba(255, 255, 255, 0.8)', + backdropFilter: 'blur(8px)' + }), + projectCard: theme => ({ + height: '100%', + display: 'flex', + flexDirection: 'column', + position: 'relative', + transition: 'transform 0.2s ease-in-out, box-shadow 0.2s ease-in-out', + borderRadius: '16px', + overflow: 'visible', // 允许内容溢出(如下拉菜单) + '&:hover': { + transform: 'translateY(-4px)', + boxShadow: theme.palette.mode === 'dark' ? '0 12px 24px rgba(0,0,0,0.3)' : '0 12px 24px rgba(0,0,0,0.1)' + } + }), + projectCardContent: { + height: '100%', + display: 'flex', + flexDirection: 'column', + p: 2 + }, + projectTitle: { + fontWeight: 700, + fontSize: '1rem', + lineHeight: 1.2, + mb: 0.25, + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap' + }, + projectDescription: { + mb: 1.5, + display: '-webkit-box', + WebkitBoxOrient: 'vertical', + WebkitLineClamp: 2, + overflow: 'hidden', + textOverflow: 'ellipsis', + height: '32px', + color: 'text.secondary', + fontSize: '0.75rem', + lineHeight: 1.4 + }, + statsContainer: { + display: 'grid', + gridTemplateColumns: 'repeat(2, 1fr)', + gap: 1, + mt: 'auto' + }, + statItem: theme => ({ + display: 'flex', + alignItems: 'center', + gap: 1, + p: 0.75, + borderRadius: '8px', + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'rgba(0,0,0,0.02)', + transition: 'background-color 0.2s', + '&:hover': { + backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.04)' + } + }), + statIconBox: (theme, color) => ({ + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: 24, + height: 24, + borderRadius: '6px', + backgroundColor: alpha(theme.palette[color].main, 0.1), + color: theme.palette[color].main + }), + cardFooter: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + mt: 2, + pt: 2, + borderTop: '1px solid', + borderColor: 'divider' + } +}; diff --git a/easy-dataset-main/styles/playground.js b/easy-dataset-main/styles/playground.js new file mode 100644 index 0000000..e6e3431 --- /dev/null +++ b/easy-dataset-main/styles/playground.js @@ -0,0 +1,82 @@ +// 模型测试页面样式 +import { alpha } from '@mui/material/styles'; + +export const playgroundStyles = theme => ({ + container: { + p: 3, + height: 'calc(100vh - 64px)', + display: 'flex', + flexDirection: 'column' + }, + mainPaper: { + p: 3, + flex: 1, + display: 'flex', + flexDirection: 'column', + mb: 2, + borderRadius: 2 + }, + controlsContainer: { + mb: 2 + }, + clearButton: { + height: '56px' + }, + divider: { + mb: 2 + }, + emptyStateBox: { + flex: 1, + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + mb: 2, + p: 2, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)', + borderRadius: 1 + }, + chatContainer: { + flex: 1, + mb: 2 + }, + modelPaper: { + height: '100%', + display: 'flex', + flexDirection: 'column', + border: `1px solid ${theme.palette.divider}`, + borderRadius: 1, + overflow: 'hidden' + }, + modelHeader: { + p: 1, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.05)' : 'primary.light', + color: theme.palette.mode === 'dark' ? 'white' : 'white', + fontWeight: 'medium', + textAlign: 'center', + display: 'flex', + alignItems: 'center', + justifyContent: 'center' + }, + modelChatBox: { + flex: 1, + overflowY: 'auto', + p: 2, + bgcolor: theme.palette.mode === 'dark' ? 'rgba(255,255,255,0.03)' : 'rgba(0,0,0,0.02)' + }, + emptyChatBox: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '100%' + }, + inputContainer: { + display: 'flex', + gap: 1, + mt: 2 + }, + sendButton: { + minWidth: '120px', + height: '56px', + marginLeft: '20px' + } +}); diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..1619f08 --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,22 @@ +FROM node:20-alpine + +WORKDIR /app + +# Install dependencies +COPY package.json . +RUN npm install + +# Copy source +COPY . . + +# Build +RUN npm run build + +# Serve with nginx +FROM nginx:alpine +COPY --from=0 /app/dist /usr/share/nginx/html +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 80 + +CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..864900b --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + YG-Dataset 数据生成平台 + + +
+ + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..0007e26 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,20 @@ +server { + listen 80; + server_name localhost; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api { + proxy_pass http://backend:8000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_cache_bypass $http_upgrade; + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..081873c --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2001 @@ +{ + "name": "yg-dataset-frontend", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "yg-dataset-frontend", + "version": "1.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.0", + "@vueuse/core": "^11.0.0", + "axios": "^1.7.0", + "element-plus": "^2.8.0", + "pinia": "^2.2.0", + "vue": "^3.5.0", + "vue-router": "^4.4.0", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "vite": "^6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmmirror.com/@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.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmmirror.com/@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/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", + "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", + "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", + "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", + "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", + "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", + "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", + "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", + "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", + "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", + "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", + "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", + "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", + "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", + "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", + "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", + "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", + "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", + "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", + "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", + "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", + "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", + "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", + "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", + "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", + "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmmirror.com/@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/@vue/compiler-core": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.30.tgz", + "integrity": "sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/shared": "3.5.30", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.30.tgz", + "integrity": "sha512-eCFYESUEVYHhiMuK4SQTldO3RYxyMR/UQL4KdGD1Yrkfdx4m/HYuZ9jSfPdA+nWJY34VWndiYdW/wZXyiPEB9g==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.30.tgz", + "integrity": "sha512-LqmFPDn89dtU9vI3wHJnwaV6GfTRD87AjWpTWpyrdVOObVtjIuSeZr181z5C4PmVx/V3j2p+0f7edFKGRMpQ5A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@vue/compiler-core": "3.5.30", + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.30.tgz", + "integrity": "sha512-NsYK6OMTnx109PSL2IAyf62JP6EUdk4Dmj6AkWcJGBvN0dQoMYtVekAmdqgTtWQgEJo+Okstbf/1p7qZr5H+bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.30.tgz", + "integrity": "sha512-179YNgKATuwj9gB+66snskRDOitDiuOZqkYia7mHKJaidOMo/WJxHKF8DuGc4V4XbYTJANlfEKb0yxTQotnx4Q==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.30.tgz", + "integrity": "sha512-e0Z+8PQsUTdwV8TtEsLzUM7SzC7lQwYKePydb7K2ZnmS6jjND+WJXkmmfh/swYzRyfP1EY3fpdesyYoymCzYfg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/shared": "3.5.30" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.30.tgz", + "integrity": "sha512-2UIGakjU4WSQ0T4iwDEW0W7vQj6n7AFn7taqZ9Cvm0Q/RA2FFOziLESrDL4GmtI1wV3jXg5nMoJSYO66egDUBw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.30", + "@vue/runtime-core": "3.5.30", + "@vue/shared": "3.5.30", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.30.tgz", + "integrity": "sha512-v+R34icapydRwbZRD0sXwtHqrQJv38JuMB4JxbOxd8NEpGLny7cncMp53W9UH/zo4j8eDHjQ1dEJXwzFQknjtQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "vue": "3.5.30" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.30.tgz", + "integrity": "sha512-YXgQ7JjaO18NeK2K9VTbDHaFy62WrObMa6XERNfNOkAhD1F1oDSf3ZJ7K6GqabZ0BvSDHajp8qfS5Sa2I9n8uQ==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "11.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-11.3.0.tgz", + "integrity": "sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "11.3.0", + "@vueuse/shared": "11.3.0", + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "11.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-11.3.0.tgz", + "integrity": "sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "11.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-11.3.0.tgz", + "integrity": "sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA==", + "license": "MIT", + "dependencies": { + "vue-demi": ">=0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/adler-32": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz", + "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.13.6", + "resolved": "https://registry.npmmirror.com/axios/-/axios-1.13.6.tgz", + "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/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/cfb": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz", + "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "crc-32": "~1.2.0" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/codepage": { + "version": "1.15.0", + "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz", + "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/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/element-plus": { + "version": "2.13.5", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.13.5.tgz", + "integrity": "sha512-dmY24fhSREfZN/PuUt0YZigMso7wWzl+B5o+YKNN15kQIn/0hzamsPU+ebj9SES0IbUqsLX1wkrzYmzU8VrVOQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "12.0.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/element-plus/node_modules/@vueuse/core": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-12.0.0.tgz", + "integrity": "sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "12.0.0", + "@vueuse/shared": "12.0.0", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/element-plus/node_modules/@vueuse/metadata": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-12.0.0.tgz", + "integrity": "sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/element-plus/node_modules/@vueuse/shared": { + "version": "12.0.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-12.0.0.tgz", + "integrity": "sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/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/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/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.npmmirror.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "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/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "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/frac": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz", + "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/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/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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.npmmirror.com/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmmirror.com/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/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/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/postcss": { + "version": "8.5.8", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.8.tgz", + "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "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/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.59.0", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.59.0.tgz", + "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.59.0", + "@rollup/rollup-android-arm64": "4.59.0", + "@rollup/rollup-darwin-arm64": "4.59.0", + "@rollup/rollup-darwin-x64": "4.59.0", + "@rollup/rollup-freebsd-arm64": "4.59.0", + "@rollup/rollup-freebsd-x64": "4.59.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", + "@rollup/rollup-linux-arm-musleabihf": "4.59.0", + "@rollup/rollup-linux-arm64-gnu": "4.59.0", + "@rollup/rollup-linux-arm64-musl": "4.59.0", + "@rollup/rollup-linux-loong64-gnu": "4.59.0", + "@rollup/rollup-linux-loong64-musl": "4.59.0", + "@rollup/rollup-linux-ppc64-gnu": "4.59.0", + "@rollup/rollup-linux-ppc64-musl": "4.59.0", + "@rollup/rollup-linux-riscv64-gnu": "4.59.0", + "@rollup/rollup-linux-riscv64-musl": "4.59.0", + "@rollup/rollup-linux-s390x-gnu": "4.59.0", + "@rollup/rollup-linux-x64-gnu": "4.59.0", + "@rollup/rollup-linux-x64-musl": "4.59.0", + "@rollup/rollup-openbsd-x64": "4.59.0", + "@rollup/rollup-openharmony-arm64": "4.59.0", + "@rollup/rollup-win32-arm64-msvc": "4.59.0", + "@rollup/rollup-win32-ia32-msvc": "4.59.0", + "@rollup/rollup-win32-x64-gnu": "4.59.0", + "@rollup/rollup-win32-x64-msvc": "4.59.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/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/ssf": { + "version": "0.11.2", + "resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz", + "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", + "license": "Apache-2.0", + "dependencies": { + "frac": "~1.1.2" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "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/vite": { + "version": "6.4.1", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.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 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.30", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.30.tgz", + "integrity": "sha512-hTHLc6VNZyzzEH/l7PFGjpcTvUgiaPK5mdLkbjrTeWSRcEfxFrv56g/XckIYlE9ckuobsdwqd5mk2g1sBkMewg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.30", + "@vue/compiler-sfc": "3.5.30", + "@vue/runtime-dom": "3.5.30", + "@vue/server-renderer": "3.5.30", + "@vue/shared": "3.5.30" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/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.npmmirror.com/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/wmf": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz", + "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/word": { + "version": "0.3.0", + "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz", + "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/xlsx": { + "version": "0.18.5", + "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz", + "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "license": "Apache-2.0", + "dependencies": { + "adler-32": "~1.3.0", + "cfb": "~1.2.1", + "codepage": "~1.15.0", + "crc-32": "~1.2.1", + "ssf": "~0.11.2", + "wmf": "~1.0.1", + "word": "~0.3.0" + }, + "bin": { + "xlsx": "bin/xlsx.njs" + }, + "engines": { + "node": ">=0.8" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..0dcd5bf --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "yg-dataset-frontend", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.0", + "vue-router": "^4.4.0", + "pinia": "^2.2.0", + "element-plus": "^2.8.0", + "@element-plus/icons-vue": "^2.3.0", + "axios": "^1.7.0", + "@vueuse/core": "^11.0.0", + "xlsx": "^0.18.5" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "vite": "^6.0.0" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..730e7b0 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js new file mode 100644 index 0000000..59fcb57 --- /dev/null +++ b/frontend/src/api/index.js @@ -0,0 +1,81 @@ +import axios from 'axios' + +const request = axios.create({ + baseURL: '/api/v1', + timeout: 60000 +}) + +// Request interceptor +request.interceptors.request.use( + config => { + return config + }, + error => { + return Promise.reject(error) + } +) + +// Response interceptor +request.interceptors.response.use( + response => { + return response.data + }, + error => { + const message = error.response?.data?.message || error.message || '请求失败' + console.error('API Error:', message) + return Promise.reject(error) + } +) + +export const projectApi = { + list: () => request.get('/projects/'), + get: (id) => request.get(`/projects/${id}`), + create: (data) => request.post('/projects/', data), + update: (id, data) => request.put(`/projects/${id}`, data), + delete: (id) => request.delete(`/projects/${id}`) +} + +export const fileApi = { + upload: (projectId, formData) => + request.post(`/projects/${projectId}/files/upload`, formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }), + list: (projectId) => request.get(`/projects/${projectId}/files/`), + get: (projectId, fileId) => request.get(`/projects/${projectId}/files/${fileId}`), + delete: (projectId, fileId) => request.delete(`/projects/${projectId}/files/${fileId}`) +} + +export const chunkApi = { + split: (projectId, data) => request.post(`/projects/${projectId}/chunks/split`, data), + list: (projectId, params) => request.get(`/projects/${projectId}/chunks/`, { params }), + get: (projectId, chunkId) => request.get(`/projects/${projectId}/chunks/${chunkId}`), + update: (projectId, chunkId, data) => request.put(`/projects/${projectId}/chunks/${chunkId}`, data), + delete: (projectId, chunkId) => request.delete(`/projects/${projectId}/chunks/${chunkId}`) +} + +export const questionApi = { + generate: (projectId, data) => request.post(`/projects/${projectId}/generate-questions`, data), + list: (projectId, params) => request.get(`/projects/${projectId}/chunks/${params.chunkId}/questions`), + update: (projectId, questionId, data) => request.put(`/projects/${projectId}/questions/${questionId}`, data), + delete: (projectId, questionId) => request.delete(`/projects/${projectId}/questions/${questionId}`) +} + +export const datasetApi = { + list: (projectId) => request.get(`/projects/${projectId}/datasets/`), + create: (projectId, data) => request.post(`/projects/${projectId}/datasets/`, data), + get: (projectId, datasetId) => request.get(`/projects/${projectId}/datasets/${datasetId}`), + delete: (projectId, datasetId) => request.delete(`/projects/${projectId}/datasets/${datasetId}`), + export: (projectId, datasetId, data) => + request.post(`/projects/${projectId}/datasets/${datasetId}/export`, data, { + responseType: 'blob' + }) +} + +export const evalApi = { + list: (projectId) => request.get(`/projects/${projectId}/eval-datasets/`), + create: (projectId, data) => request.post(`/projects/${projectId}/eval-datasets/`, data), + run: (projectId, evalId) => request.post(`/projects/${projectId}/eval-datasets/${evalId}/evaluate`), + getResults: (projectId, taskId) => request.get(`/projects/${projectId}/eval-tasks/${taskId}`) +} + +export default request diff --git a/frontend/src/main.js b/frontend/src/main.js new file mode 100644 index 0000000..6a19c71 --- /dev/null +++ b/frontend/src/main.js @@ -0,0 +1,21 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' + +import App from './App.vue' +import router from './router' + +const app = createApp(App) + +// Register all Element Plus icons +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.use(createPinia()) +app.use(router) +app.use(ElementPlus) + +app.mount('#app') diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js new file mode 100644 index 0000000..6e45dc2 --- /dev/null +++ b/frontend/src/router/index.js @@ -0,0 +1,67 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const routes = [ + { + path: '/', + name: 'Home', + component: () => import('@/views/HomeView.vue') + }, + { + path: '/project/:id', + name: 'Project', + component: () => import('@/views/ProjectView.vue'), + children: [ + { + path: '', + redirect: to => `/project/${to.params.id}/files` + }, + { + path: 'files', + name: 'ProjectFiles', + component: () => import('@/views/project/FileManage.vue') + }, + { + path: 'split', + name: 'ProjectSplit', + component: () => import('@/views/project/TextSplit.vue') + }, + { + path: 'questions', + name: 'ProjectQuestions', + component: () => import('@/views/project/QuestionManage.vue') + }, + { + path: 'datasets', + name: 'ProjectDatasets', + component: () => import('@/views/project/DatasetManage.vue') + }, + { + path: 'eval', + name: 'ProjectEval', + component: () => import('@/views/project/EvalManage.vue') + }, + { + path: 'settings', + name: 'ProjectSettings', + component: () => import('@/views/project/Settings.vue') + } + ] + }, + { + path: '/playground', + name: 'Playground', + component: () => import('@/views/PlaygroundView.vue') + }, + { + path: '/data-square', + name: 'DataSquare', + component: () => import('@/views/DataSquareView.vue') + } +] + +const router = createRouter({ + history: createWebHistory(), + routes +}) + +export default router diff --git a/frontend/src/views/DataSquareView.vue b/frontend/src/views/DataSquareView.vue new file mode 100644 index 0000000..c0b1757 --- /dev/null +++ b/frontend/src/views/DataSquareView.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/frontend/src/views/HomeView.vue b/frontend/src/views/HomeView.vue new file mode 100644 index 0000000..849de72 --- /dev/null +++ b/frontend/src/views/HomeView.vue @@ -0,0 +1,1239 @@ + + + + + diff --git a/frontend/src/views/PlaygroundView.vue b/frontend/src/views/PlaygroundView.vue new file mode 100644 index 0000000..c65ca95 --- /dev/null +++ b/frontend/src/views/PlaygroundView.vue @@ -0,0 +1,415 @@ + + + + + diff --git a/frontend/src/views/ProjectView.vue b/frontend/src/views/ProjectView.vue new file mode 100644 index 0000000..0bd3581 --- /dev/null +++ b/frontend/src/views/ProjectView.vue @@ -0,0 +1,363 @@ + + + + + diff --git a/frontend/src/views/project/DatasetManage.vue b/frontend/src/views/project/DatasetManage.vue new file mode 100644 index 0000000..ef0ef36 --- /dev/null +++ b/frontend/src/views/project/DatasetManage.vue @@ -0,0 +1,445 @@ + + + + + diff --git a/frontend/src/views/project/EvalManage.vue b/frontend/src/views/project/EvalManage.vue new file mode 100644 index 0000000..2336c36 --- /dev/null +++ b/frontend/src/views/project/EvalManage.vue @@ -0,0 +1,405 @@ + + + + + diff --git a/frontend/src/views/project/FileManage.vue b/frontend/src/views/project/FileManage.vue new file mode 100644 index 0000000..d8ff060 --- /dev/null +++ b/frontend/src/views/project/FileManage.vue @@ -0,0 +1,611 @@ + + + + + diff --git a/frontend/src/views/project/QuestionManage.vue b/frontend/src/views/project/QuestionManage.vue new file mode 100644 index 0000000..9f8a29f --- /dev/null +++ b/frontend/src/views/project/QuestionManage.vue @@ -0,0 +1,320 @@ + + + + + diff --git a/frontend/src/views/project/Settings.vue b/frontend/src/views/project/Settings.vue new file mode 100644 index 0000000..d2e9c09 --- /dev/null +++ b/frontend/src/views/project/Settings.vue @@ -0,0 +1,202 @@ + + + + + diff --git a/frontend/src/views/project/TextSplit.vue b/frontend/src/views/project/TextSplit.vue new file mode 100644 index 0000000..dabaedb --- /dev/null +++ b/frontend/src/views/project/TextSplit.vue @@ -0,0 +1,470 @@ + + + + + diff --git a/frontend/vite.config.js b/frontend/vite.config.js new file mode 100644 index 0000000..54e4487 --- /dev/null +++ b/frontend/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import { resolve } from 'path' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': resolve(__dirname, 'src') + } + }, + server: { + port: 3000, + proxy: { + '/api': { + target: 'http://localhost:8000', + changeOrigin: true + } + } + } +}) diff --git a/开发计划.md b/开发计划.md new file mode 100644 index 0000000..0058028 --- /dev/null +++ b/开发计划.md @@ -0,0 +1,117 @@ +# YG-Dataset 开发计划 + +## 项目状态: 基础架构完成 ✓ + +--- + +## 阶段 1: 基础架构 (第1周) - 已完成 ✓ + +### 1.1 后端基础搭建 +- [x] 创建 FastAPI 项目结构 +- [x] 配置 requirements.txt +- [x] 创建 docker-compose.yml +- [x] 创建基础 API 路由框架 +- [x] 创建数据库模型 (Project, File, Chunk, Question, Dataset, EvalDataset, ModelConfig, Task) +- [ ] 配置 alembic 迁移(可选) + +### 1.2 前端基础搭建 +- [x] 创建 Vue 3 + Vite 项目 +- [x] 安装 Element Plus, Pinia, Vue Router +- [x] 创建基础路由结构 +- [x] 创建 HomeView 页面 +- [x] 创建 ProjectView 布局 +- [x] 创建各个子页面 (FileManage, TextSplit, QuestionManage, DatasetManage, EvalManage, Settings) +- [x] 创建 PlaygroundView 和 DataSquareView + +--- + +## 阶段 2: 核心功能 (第2-3周) + +### 2.1 文件处理模块 +- [x] 实现文件上传 API +- [x] 实现 PDF 解析 (pdfplumber) +- [x] 实现 Excel/CSV 解析 (pandas) +- [x] 实现 DOCX 解析 +- [x] 前端文件管理页面 +- [ ] 实现 EPUB 解析 +- [ ] 实现 Markdown/TXT 解析 + +### 2.2 文本分割模块 +- [x] 实现多种分割算法 (recursive, markdown_structure, token, code, custom) +- [x] 目录提取功能 +- [x] 前端分割配置页面 + +--- + +## 阶段 3: 数据生成 (第4周) + +### 3.1 问题生成 +- [ ] LLM 集成 (OpenAI, Anthropic, Ollama) +- [ ] 批量问题生成 +- [ ] 问题编辑界面 + +### 3.2 数据集管理 +- [x] 数据集创建/编辑 +- [x] 导出功能 (Alpaca/ShareGPT/LLaMA Factory) - 基础实现 + +--- + +## 阶段 4: 评估系统 (第5周) + +### 4.1 评估功能 +- [x] 评估数据集管理 +- [ ] 评估运行 +- [ ] 结果展示 + +### 4.2 盲测系统 +- [ ] 盲测任务管理 +- [ ] 模型对比 + +--- + +## 阶段 5: UI 优化 (第6周) + +- [ ] 完善所有页面 +- [ ] 响应式适配 +- [ ] 暗色模式 +- [ ] 性能优化 + +--- + +## 项目文件结构 + +``` +YG-Datasets/ +├── backend/ # FastAPI 后端 +│ ├── app/ +│ │ ├── api/v1/ # API 路由 +│ │ │ ├── projects/ # 项目管理 +│ │ │ ├── files/ # 文件处理 +│ │ │ ├── chunks/ # 文本分割 +│ │ │ ├── questions/ # 问题管理 +│ │ │ ├── datasets/ # 数据集 +│ │ │ └── eval/ # 评估 +│ │ ├── models/ # SQLAlchemy 模型 +│ │ ├── schemas/ # Pydantic 模型 +│ │ ├── services/ # 业务逻辑 +│ │ │ ├── file_processor/ +│ │ │ └── text_splitter/ +│ │ └── core/ # 核心配置 +│ ├── requirements.txt +│ └── Dockerfile +├── frontend/ # Vue 3 前端 +│ ├── src/ +│ │ ├── views/ # 页面 +│ │ ├── api/ # API 封装 +│ │ └── router/ # 路由 +│ ├── package.json +│ └── Dockerfile +├── docker-compose.yml # 容器编排 +├── 开发计划.md +├── bug修改.md +└── 项目架构.md +``` + +--- + +*最后更新: 2026-03-17* diff --git a/项目架构.md b/项目架构.md new file mode 100644 index 0000000..581c312 --- /dev/null +++ b/项目架构.md @@ -0,0 +1,225 @@ +# YG-Dataset 架构设计文档 + +## 一、项目概述 + +YG-Dataset 是一个**数据生成平台**,用于将各类文档(PDF、DOCX、Excel、CSV等)转换为可用于训练大语言模型的高质量数据集。 + +### 1.1 核心能力 +- 多格式文档解析与处理 +- 智能文本分割(多种算法) +- 基于 LLM 的问题自动生成 +- 数据集管理与多格式导出 +- 模型评估与盲测系统 + +### 1.2 技术架构 +``` +┌─────────────────────────────────────────────────────────────┐ +│ Vue 3 Frontend │ +│ (Element Plus + Pinia + Vue Router) │ +└─────────────────────────┬───────────────────────────────────┘ + │ HTTP/WebSocket +┌─────────────────────────▼───────────────────────────────────┐ +│ FastAPI Backend │ +│ (REST API + WebSocket) │ +├─────────────────────────────────────────────────────────────┤ +│ Services Layer │ +│ ├── FileProcessor (PDF/DOCX/Excel/CSV解析) │ +│ ├── TextSplitter (6种分割算法) │ +│ ├── LLMService (问题生成/评估) │ +│ └── ExportService (多格式导出) │ +├─────────────────────────────────────────────────────────────┤ +│ Data Access Layer (SQLAlchemy 2.0) │ +└─────────────────────────┬───────────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────────┐ +│ PostgreSQL + Redis │ +│ (主数据库 + 缓存/任务队列) │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 二、系统架构 + +### 2.1 分层架构 + +``` +┌────────────────────────────────────────────────────────┐ +│ Presentation Layer │ +│ Vue 3 + Element Plus + Pinia + Vue Router │ +├────────────────────────────────────────────────────────┤ +│ API Layer │ +│ FastAPI + Pydantic + CORS Middleware │ +├────────────────────────────────────────────────────────┤ +│ Service Layer │ +│ FileProcessor / TextSplitter / LLM / Export │ +├────────────────────────────────────────────────────────┤ +│ Repository Layer │ +│ SQLAlchemy Models + Async Session │ +├────────────────────────────────────────────────────────┤ +│ Infrastructure │ +│ PostgreSQL / Redis / File System │ +└────────────────────────────────────────────────────────┘ +``` + +### 2.2 数据流架构 + +``` +文件上传 → 文档解析 → 文本提取 → 分割处理 → 问题生成 → 数据集构建 → 导出 + ↓ + 评估/盲测 +``` + +--- + +## 三、模块设计 + +### 3.1 后端模块 + +| 模块 | 职责 | 关键类/函数 | +|------|------|-------------| +| `app/api/v1/` | REST API 路由 | Router 配置 | +| `app/models/` | SQLAlchemy ORM 模型 | Project, File, Chunk, Question 等 | +| `app/schemas/` | Pydantic 数据验证 | Request/Response 模型 | +| `app/services/` | 业务逻辑 | FileProcessor, TextSplitter, LLMWrapper | +| `app/core/` | 核心配置 | Config, Database, Security | + +### 3.2 前端模块 + +| 目录 | 职责 | +|------|------| +| `views/` | 页面组件 (Home, Project, Playground, DataSquare) | +| `components/` | 可复用组件 | +| `stores/` | Pinia 状态管理 | +| `api/` | API 封装 | +| `router/` | 路由配置 | + +--- + +## 四、API 设计 + +### 4.1 API 路由结构 + +``` +/api/v1/ +├── /projects/ # 项目管理 +│ ├── GET / # 列表 +│ ├── POST / # 创建 +│ ├── GET /{id} # 详情 +│ └── DELETE /{id} # 删除 +├── /files/ # 文件处理 +│ ├── POST /upload # 上传 +│ ├── POST /process # 处理 +│ └── GET /{id} # 状态 +├── /chunks/ # 文本分割 +│ ├── POST /split # 执行分割 +│ └── GET / # 列表 +├── /questions/ # 问题管理 +│ ├── POST /generate # 生成 +│ └── GET / # 列表 +├── /datasets/ # 数据集 +│ ├── GET / # 列表 +│ ├── POST / # 创建 +│ └── POST /export # 导出 +└── /eval/ # 评估系统 + ├── POST /generate # 生成评估题 + └── POST /run # 运行评估 +``` + +--- + +## 五、数据库设计 + +### 5.1 核心表 + +| 表名 | 说明 | +|------|------| +| `projects` | 项目表 | +| `files` | 文件表(上传的文件) | +| `chunks` | 文本块表(分割后的片段) | +| `tags` | 标签/领域树 | +| `questions` | 问题表 | +| `datasets` | 数据集表 | +| `eval_datasets` | 评估数据集表 | +| `model_configs` | 模型配置表 | +| `tasks` | 任务表 | + +### 5.2 ER 关系 + +``` +Project 1──∞ File +Project 1──∞ Chunk +Project 1──∞ Question +Project 1──∞ Dataset +File 1──∞ Chunk +Chunk 1──∞ Question +Chunk *──* Tag +Dataset *──* Question +``` + +--- + +## 六、部署架构 + +### 6.1 Docker Compose + +```yaml +services: + postgres: # 主数据库 + redis: # 缓存/队列 + backend: # FastAPI 服务 + frontend: # Vue 3 服务 +``` + +### 6.2 目录结构 + +``` +YG-Datasets/ +├── backend/ # FastAPI 后端 +│ ├── app/ +│ │ ├── api/ # API 路由 +│ │ ├── models/ # ORM 模型 +│ │ ├── schemas/ # Pydantic 模型 +│ │ ├── services/ # 业务逻辑 +│ │ └── core/ # 核心配置 +│ ├── alembic/ # 数据库迁移 +│ └── requirements.txt +├── frontend/ # Vue 3 前端 +│ ├── src/ +│ │ ├── views/ # 页面 +│ │ ├── components/ +│ │ ├── stores/ # Pinia +│ │ └── api/ +│ └── package.json +├── docker-compose.yml +└── README.md +``` + +--- + +## 七、技术选型理由 + +| 技术 | 选型理由 | +|------|----------| +| FastAPI | 高性能、异步支持、自动 API 文档 | +| SQLAlchemy 2.0 | 异步 ORM、类型安全 | +| PostgreSQL | 可靠的关系型数据库 | +| Redis | 缓存、任务队列 | +| Vue 3 | Composition API、更好的 TypeScript 支持 | +| Element Plus | 成熟的 Vue 3 UI 组件库 | +| Pinia | 轻量级、TypeScript 友好 | +| Docker Compose | 简化开发/部署 | + +--- + +## 八、安全考虑 + +- CORS 配置(生产环境需限制域名) +- 文件上传大小限制 +- API 请求验证 +- 数据库连接池管理 + +--- + +*文档版本: v1.0* +*更新日期: 2026-03-17*